【Go学习】-01-1-入门及变量常量指针

【Go学习】-01-1-入门及变量常量指针

  • [1 入门介绍](#1 入门介绍)
    • [1.1 为什么使用Go](#1.1 为什么使用Go)
    • [1.2 Go下载](#1.2 Go下载)
    • [1.3 环境配置](#1.3 环境配置)
  • [2 入门案例](#2 入门案例)
  • [3 开发工具](#3 开发工具)
  • [4 变量概述](#4 变量概述)
    • [4.1 变量声明](#4.1 变量声明)
      • [4.1.1 标准格式](#4.1.1 标准格式)
      • [4.1.2 基本类型](#4.1.2 基本类型)
      • [4.1.3 不指明变量类型](#4.1.3 不指明变量类型)
      • [4.1.4 批量格式](#4.1.4 批量格式)
      • [4.1.5 简短格式](#4.1.5 简短格式)
    • [4.2 初始化变量](#4.2 初始化变量)
    • [4.3 交换变量](#4.3 交换变量)
    • [4.4 匿名变量](#4.4 匿名变量)
    • [4.5 作用域](#4.5 作用域)
      • [4.5.1 局部变量](#4.5.1 局部变量)
      • [4.5.2 全局变量](#4.5.2 全局变量)
      • [4.5.3 形式参数](#4.5.3 形式参数)
    • [4.6 变量的生命周期](#4.6 变量的生命周期)
  • [5 变量类型](#5 变量类型)
    • [5.1 整型](#5.1 整型)
    • [5.2 浮点型](#5.2 浮点型)
    • [5.3 布尔型](#5.3 布尔型)
    • [5.4 字符类型](#5.4 字符类型)
      • [5.4.1 UTF-8 和 Unicode 有何区别?](#5.4.1 UTF-8 和 Unicode 有何区别?)
    • [5.5 字符串型](#5.5 字符串型)
      • [5.5.1 字符串的应用](#5.5.1 字符串的应用)
        • [5.5.1.1 拼接符"+"](#5.5.1.1 拼接符“+”)
        • [5.5.1.2 遍历](#5.5.1.2 遍历)
        • [5.5.1.3 格式化](#5.5.1.3 格式化)
        • [5.5.1.4 查找](#5.5.1.4 查找)
    • [5.6 类型转换](#5.6 类型转换)
      • [5.6.1 修改字符串](#5.6.1 修改字符串)
        • [5.6.1.1 小练习](#5.6.1.1 小练习)
      • [5.6.2 字符串与其他数据类型的转换](#5.6.2 字符串与其他数据类型的转换)
        • [5.6.2.1 文字游戏](#5.6.2.1 文字游戏)
    • [5.7 类型别名](#5.7 类型别名)
  • [6 常量](#6 常量)
    • [6.1 iota 常量生成器](#6.1 iota 常量生成器)
  • [7 指针](#7 指针)
    • [7.1 如何理解指针](#7.1 如何理解指针)
    • [7.2 使用指针修改值](#7.2 使用指针修改值)
    • [7.3 创建指针的另一种方法](#7.3 创建指针的另一种方法)
    • [7.4 指针小案例](#7.4 指针小案例)
  • [8 注释](#8 注释)
  • [9 关键字和标识符](#9 关键字和标识符)
  • [10 运算符优先级](#10 运算符优先级)

1 入门介绍

1.1 为什么使用Go

  1. 简单好记的关键词和语法。轻松上手,简单易学。
  2. 更高的效率。比Java,C++等拥有更高的编译速度,同时运行效率媲美C,同时开发效率非常高。
  3. 生态强大,网络上库很丰富,很多功能使用Go开发非常简单。
  4. 语法检查严格,高安全性。
  5. 严格的依赖管理,go mod命令。
  6. Go拥有强大的编译检查、严格的编码规范和完整的软件生命周期工具,具有很强的稳定性,稳定压倒一切。
  7. 跨平台交叉编译,windows就可以编译出mac,linux上可执行的程序。
  8. 异步编程复杂度低,易维护,GO 语言中 Channel 设计,异步程序写起来非常自然。
  9. 语言层面支持并发,go关键字(协程)使得go的并发效率极高。
  10. 严格的语法规范,所有程序员写出来的代码都是一样的,对大团队来说,非常友好。
  11. Go 的并发、性能、安全性、易于部署等特性,使它很容易成为"云原生语言"。容器和云的使用上非常广

1.2 Go下载

我们使用go1.17.5的版本,windows的zip包,下载地址:https://go.dev/dl/

1.3 环境配置

将go的压缩包,解压到D:\software_code\Go\1.17.5目录下

要使用go首先需要配置几个环境变量:

1.GOROOT:go语言所在的目录,用于全局执行go相关的命令,在系统变量中添加GOROOT

​ path中也需要配置

%GOROOT%\bin
  1. GOPATH:工作目录,工程代码存放的位置,此目录下,一个文件夹就是一个工程

  2. GOPROXY:代理,由于go需要翻墙使用,需要配置代理,有好心人做了代理

    https://goproxy.io,direct
    

地址:https://goproxy.io/zh/ 可以去看文档

  1. go env 可以检查环境变量的配置是否正确

2 入门案例

在我们的GOPATH路径下,新建一个文件夹,hello

进入hello目录,运行cmd命令

go 复制代码
# 运行下面的命令
go mod init hello

会生成一个go.mod文件,如果学过maven的话,可以将其当成maven的pom.xml,用于管理依赖的。

接下来,写程序

新建man.go

在其中输入以下内容:

go 复制代码
// package 定义包名 main 包名
package main

// import 引用库 fmt 库名
import "fmt"

// func 定义函数 main 函数名
func main() {
	// fmt 包名 . 调用 Print 函数,并且输出定义的字符串
	fmt.Print("Hello Golang")
}

运行以下命令:

go 复制代码
go run main.go

我们的字符串也就打印出来了

3 开发工具

工欲善其事,必先利其器,这里我们选用GoLand做为我们的开发IDE。

下载GoLand,自行找激活码

直接运行即可

注意在设置中,go相关的配置

4 变量概述

4.1 变量声明

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

我们从计算机系统的角度来讲,变量就是一段或者多段内存,用于存储数据

4.1.1 标准格式

go 复制代码
var 变量名 变量类型

变量声明以关键字var开头,变量类型后置,行尾无须分号

举个例子

go 复制代码
//声明了一个名为age的变量,类型为int
var age int

如果你学过C语言,就会体会到这样声明的好处,比如C语言这样声明:int* a, b ,那么只有a是指针,b不是,这样会使人迷惑,如果想要两个变量都为指针,需要这样定义:int *a,*b

在go语言中,我们使用这样的声明方式:var a,b *int,就可以轻松的将a,b都声明为指针。

变量的命名规则遵循驼峰命名法,即首个单词小写,每个新单词的首字母大写,例如: startDate

4.1.2 基本类型

计算机中数据存储的最小单位为bit(位),0或者1

byte:计算机中数据的基本单元,1字节=8bit,数据在计算机中存储或者计算,至少为1个字节

  • bool
  • string
  • int(随系统,一般是占用4个字节)、int8(占一个字节)、int16(占两个字节)、int32(占4个字节)、int64(占8个字节)
  • uint(无符号整数)、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

有符号和无符号的区别:int8 范围 -128-127,uint8 范围:0-255

当一个变量被声明之后,系统自动赋予它该类型的零值:

int 为 0float 为 0.0bool 为 falsestring 为空字符串指针为 nil

所有的内存在 Go 中都是经过初始化的。

go 复制代码
package main

import "fmt"

var age int
func main() {

	fmt.Println(age);
}

输出

0

4.1.3 不指明变量类型

go 复制代码
//设置游戏中角色的初始等级为1
var level = 1;

像上面这种声明变量的方式,并没有指明类型,Go语言中,在编译时会自动推导类型

我们可以使用 %T获取变量类型

go 复制代码
fmt.Printf("%T", level)

进行类型输出。

go 复制代码
package main

import "fmt"

var level = 1

func main() {
	fmt.Printf("%T",level)
}

输出

int

4.1.4 批量格式

觉得每行都用 var 声明变量比较烦琐?Go语言提供了批量声明的方式

go 复制代码
var (
    a int
    b string
    c []float32
)
go 复制代码
package main

import "fmt"


var (
	a int
	b string
	c []float32
)
func main() {
    //%d 整数占位符,%s 字符串占位符, %f 浮点数占位符(默认精度为6)
	fmt.Printf("%d,%s,%f",a,b,c)
}

输出

0,,[]

4.1.5 简短格式

我们可以省略var关键字,这样写起来更加便捷

go 复制代码
//i是变量名 1 是值(或者表达式)
i := 1

上面讲过,如果不指明类型,直接赋值,Go会自动推导类型

使用简短格式有以下限制:

  1. 定义变量,同时显式初始化
  2. 不能提供数据类型
  3. 只能用在函数内部
go 复制代码
package main

import "fmt"

//不能
//aa :=1
func main() {
	aa :=1
	fmt.Println(aa)
}

简短变量声明被广泛用于大部分的局部变量的声明和初始化,var 形式的声明语句往往用于需要显式指定变量类型的地方。

在go中,局部变量必须使用!

4.2 初始化变量

go 复制代码
//创建了一个游戏角色 初始等级为1
var level int = 1
go 复制代码
//短变量声明
level := 1

以下的代码会出错:

go 复制代码
package main

func main() {
	
	var level int = 1
    // 再次声明并赋值 会报错 no new variables on left side of := (左边的变量已经被声明了,不能重复声明)
	level := 1
}

但是有特例

比如:net.Dial 提供按指定协议和地址发起网络连接,这个函数有两个返回值,一个是连接对象(conn),一个是错误对象(err)

正常的写法:

go 复制代码
package main

import (
	"fmt"
	"net"
)
func main() {

	var conn net.Conn
	var err error
	conn, err = net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
	fmt.Println(err)
}

短变量的写法:

go 复制代码
package main

import (
	"fmt"
	"net"
)

func main() {

	conn, err := net.Dial("tcp", "127.0.0.1:8080")
	conn1, err := net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
	fmt.Println(conn1)
	fmt.Println(err)
}

输出

<nil>
dial tcp 127.0.0.1:8080: connectex: No connection could be made because the target machine actively refused it.

在多个短变量声明和赋值中,至少有一个新声明的变量出现在左值中,即便其他变量名可能是重复声明的,编译器也不会报错

4.3 交换变量

变量交换,比如a=100,b=200,交换之后 a=200,b=100

如果是你,你会怎么样进行实现呢?

第一种

go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200
	var c int
	c = b
	b = a
	a = c
	fmt.Printf("a=%d,b=%d",a,b)
}

第二种

go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200

	a = a^b
	b = b^a
	a = a^b
	fmt.Printf("a=%d,b=%d",a,b)
}

a = a ^ b a变成了a ^ b = 100 ^ 200

b = b ^ a 异或满足 x ^ y ^ y = x 的性质,所以b=b ^ a ^ b b=200⊕(100⊕200)=100

a = a ^ b 现在a是a ^ b,也就是(100 ^ 200),同样利用异或性质 x ^ y ^ x = y,现在b是100 a=(100⊕200)⊕100=200

利用异或完成交换。

第三种

go 复制代码
package main

import "fmt"

func main() {
	a := 100
	b := 200
	b,a = a,b
	fmt.Printf("a=%d,b=%d",a,b)
}

应该有点体会到Go语言编程的快捷,方便以及强大了吧

4.4 匿名变量

使用多重赋值时,如果不需要在左值中接受变量,可以使用匿名变量

比如上面的例子:

go 复制代码
package main

import (
	"fmt"
	"net"
)
func main() {

    //conn, err := net.Dial("tcp", "127.0.0.1:8080")
    //如果不想接收err的值,那么可以使用_表示,这就是匿名变量
    conn, _ := net.Dial("tcp", "127.0.0.1:8080")
	fmt.Println(conn)
}

匿名变量以"_"下划线表示

匿名变量不占用命名空间,也不会分配内存。匿名变量可以重复声明使用

"_"本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

go 复制代码
package main

import (
	"fmt"
	"net"
)

func main() {

	conn, _ := net.Dial("tcp", "127.0.0.1:8080")
	//匿名变量可以重复声明
	conn1, _ := net.Dial("tcp", "127.0.0.1:8080")
	// 匿名变量不可以直接开头
	// _ :=1
	fmt.Println(conn)
	fmt.Println(conn1)
}

4.5 作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域

了解变量的作用域对我们学习Go语言来说是比较重要的,因为Go语言(静态语言)会在编译时检查每个变量是否使用过,一旦出现未使用的变量,就会报编译错误

如果不能理解变量的作用域,就有可能会带来一些不明所以的编译错误。

根据变量定义位置的不同,可以分为以下三个类型:

  • 函数内定义的变量称为局部变量
  • 函数外定义的变量称为全局变量
  • 函数定义中的变量称为形式参数

4.5.1 局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。

局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。

go 复制代码
package main
import (
    "fmt"
)
func main() {
    //声明局部变量 a 和 b 并赋值
    var a int = 3
    var b int = 4
    //声明局部变量 c 并计算 a 和 b 的和
    c := a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

4.5.2 全局变量

在函数体外声明的变量称之为全局变量,全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用"import"关键字引入全局变量所在的源文件之后才能使用这个全局变量。

全局变量声明必须以 var 关键字开头,如果想要在外部包中使用全局变量的首字母必须大写

go 复制代码
package main
import "fmt"
//声明全局变量
var c int
func main() {
    //声明局部变量
    var a, b int
    //初始化参数
    a = 3
    b = 4
    c = a + b
    fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}

Go语言程序中全局变量与局部变量名称可以相同,但是函数体内的局部变量会被优先考虑。

go 复制代码
package main
import "fmt"
//声明全局变量
var bb float32 = 3.14
func main() {
	bb := 3
	fmt.Println(bb)
}
//执行结果 3

4.5.3 形式参数

在定义函数时函数名后面括号中的变量叫做形式参数(简称形参)。形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值。

形式参数会作为函数的局部变量来使用

go 复制代码
package main
import (
    "fmt"
)
//全局变量 a
var a int = 13
func main() {
    //局部变量 a 和 b
    var a int = 3
    var b int = 4
    fmt.Printf("main() 函数中 a = %d\n", a)
    fmt.Printf("main() 函数中 b = %d\n", b)
    c := sum(a, b)
    fmt.Printf("main() 函数中 c = %d\n", c)
}
func sum(a, b int) int {
    fmt.Printf("sum() 函数中 a = %d\n", a)
    fmt.Printf("sum() 函数中 b = %d\n", b)
    num := a + b
    return num
}

至此,Go语言变量相关的知识,我们就掌握了

4.6 变量的生命周期

变量的生命周期指的是在程序运行期间变量有效存在的时间间隔。

变量的生命周期与变量的作用域有不可分割的联系:

  1. 全局变量:它的生命周期和整个程序的运行周期是一致的;
  2. 局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
  3. 形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。

go的内存中应用了两种数据结构用于存放变量:

  1. 堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态加入到堆上(堆被扩张)。当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减);
  2. 栈(stack):栈又称堆栈, 用来存放程序暂时创建的局部变量,也就是我们函数的大括号{ }中定义的局部变量。

栈是先进后出,往栈中放元素的过程,称为入栈,取元素的过程称为出栈。

栈可用于内存分配,栈的分配和回收速度非常快

在程序的编译阶段,编译器会根据实际情况自动选择或者上分配局部变量的存储空间,不论使用 var 还是 new 关键字声明变量都不会影响编译器的选择。

go 复制代码
var global *int
func f() {
    var x int
    x = 1
    global = &x
}
func g() {
    y := new(int)
    *y = 1
}

上述代码中,函数 f 里的变量 x 必须在堆上分配,因为它在函数退出后依然可以通过包一级的 global 变量找到,虽然它是在函数内部定义的。

用Go语言的术语说,这个局部变量 x 从函数 f 中逃逸了。

相反,当函数 g 返回时,变量 y 不再被使用,也就是说可以马上被回收的。因此,y 并没有从函数 g 中逃逸,编译器可以选择在栈上分配 *y 的存储空间,也可以选择在堆上分配,然后由Go语言的 GC(垃圾回收机制)回收这个变量的内存空间。

5 变量类型

5.1 整型

Go语言同时提供了有符号和无符号的整数类型。

  • 有符号整型:int、int8、int64、int32、int64
  • 无符号整型:uint、uint8、uint64、uint32、uint64、uintptr

有符号整型范围:-2^(n-1) 到 2^(n-1)-1

无符号整型范围: 0 到 2^n-1

实际开发中由于编译器和计算机硬件的不同,int 和 uint 所能表示的整数大小会在 32bit 或 64bit 之间变化。

uint在硬件开发中使用

用来表示 Unicode 字符的 rune 类型int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。同样,byteuint8 也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

无符号的整数类型 uintptr,它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。

在二进制传输、读写文件的结构描述时,为了保持文件的结构不会受到不同编译目标平台字节长度的影响,不要使用 int 和 uint

5.2 浮点型

Go语言支持两种浮点型数:

  1. float32 : 范围 约1.4e-45 到 约3.4e38
  2. float64 :范围约4.9e-324 到 约1.8e308
go 复制代码
floatStr1 := 3.2
//保留小数点位数
fmt.Printf("%.2f\n", floatStr1)

算术规范由 IEEE754 浮点数国际标准定义,该浮点数规范被所有现代的 CPU 支持

通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散,并且 float32 能精确表示的正整数并不是很大。

go 复制代码
var f float32 = 1 << 24;
fmt.Println(f == f+1) // true

浮点数在声明的时候可以只写整数部分或者小数部分

go 复制代码
var e = .71828 // 0.71828
var f = 1.     // 1
fmt.Printf("%.5f,%.1f",e,f)

很小或很大的数最好用科学计数法书写,通过 e 或 E 来指定指数部分

go 复制代码
var avogadro = 6.02214129e23  // 阿伏伽德罗常数
var planck   = 6.62606957e-34 // 普朗克常数
fmt.Printf("%f,%.35f",avogadro,planck)

5.3 布尔型

在Go语言中,以bool类型进行声明:

go 复制代码
var 变量名 bool

==,>,<<=, >=,&&(AND),||(OR)等都会产生bool值

go 复制代码
var aVar = 10
aVar == 5  // false
aVar == 10 // true
aVar != 5  // true
aVar != 10 // false

Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。

如果其中一个值是常量,那么另外一个值可以不是常量,但是类型必须和该常量类型相同。

如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。

&&(AND),||(OR)是具有短路行为的,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。(&&优先级高于||)

go 复制代码
var a = 10
	//因为a>11已经不满足了,所以a < 30不会走,整个表达式为false
	if(a > 11 && a < 30){
		fmt.Println("正确")
	}else{
		fmt.Println("错误")
	}

	//因为a > 5已经满足了,所以a < 30不会走,整个表达式为true
	if(a > 5 || a < 30){
		fmt.Println("正确")
	}else{
		fmt.Println("错误")
	}

布尔型数据只有true和false,且不能参与任何计算以及类型转换

5.4 字符类型

Go语言的字符有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

byte 类型是 uint8 的别名,rune 类型是int32的别名

ASCII 码的一个字符占一个字节

ASCII 定义 128 个字符,由码位 0 -- 127 标识。它涵盖英文字母,拉丁数字和其他一些字符。

字符的定义:

go 复制代码
//使用单引号 表示一个字符
var ch byte = 'A'
//在 ASCII 码表中,A 的值是 65,也可以这么定义
var ch byte = 65
//65使用十六进制表示是41,所以也可以这么定义 \x 总是紧跟着长度为 2 的 16 进制数
var ch byte = '\x41'
//65的八进制表示是101,所以使用八进制定义 \后面紧跟着长度为 3 的八进制数
var ch byte = '\101'

fmt.Printf("%c",ch)

Unicode 是 ASCII 的超集,它定义了 1,114,112 个代码点的代码空间。 Unicode 版本 10.0 涵盖 139 个现代和历史文本集(包括符文字母,但不包括 Klingon )以及多个符号集。

Go语言同样支持 Unicode(UTF-8), 用rune来表示, 在内存中使用 int 来表示。

在书写 Unicode 字符时,需要在 16 进制数之前加上前缀\u或者\U。如果需要使用到 4 字节,则使用\u前缀,如果需要使用到 8 个字节,则使用\U前缀。

go 复制代码
func main() {
	var ch rune = '\u0041'
	var ch1 int64 = '\U00000041'
	//格式化说明符%c用于表示字符,%v或%d会输出用于表示该字符的整数,%U输出格式为 U+hhhh 的字符串。
	fmt.Printf("%c,%c,%v,%v,%U,%U", ch, ch1, ch, ch1, ch, ch1)
}

输出

A,A,65,65,U+0041,U+0041

Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

5.4.1 UTF-8 和 Unicode 有何区别?

Unicode 与 ASCII 类似,都是一种字符集。

字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID,例如 a 在 Unicode 与 ASCII 中的编码都是 97。汉字"你"在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。

UTF-8 是编码规则,将 Unicode 中字符的 ID 以某种方式进行编码,UTF-8 的是一种变长编码规则,从 1 到 4 个字节不等。编码规则如下:

  • 0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
  • 从 128 到 0x10ffff 表示其他字符。

根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节

广义的 Unicode 指的是一个标准,它定义了字符集及编码规则,即 Unicode 字符集和 UTF-8、UTF-16 编码等。

5.5 字符串型

一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是 UTF-8 字符的一个序列。

字符串的定义:

go 复制代码
var mystr string = "hello"

go语言从底层就支持UTF-8编码。

UTF-8 是一种被广泛使用的编码格式,是文本文件的标准编码。

由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用 1 至 4 个字节,这与其它编程语言不同。

Go语言这样做不仅减少了内存和硬盘空间占用,同时也不用像其它语言那样需要对使用 UTF-8 字符集的文本进行编码和解码。

字符串是一种值类型,且值不可变,即创建某个文本后将无法再次修改这个文本的内容。

当字符为 ASCII 码表上的字符时则占用 1 个字节

字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:

  1. \n:换行符
  2. \r:回车符
  3. \t:tab 键
  4. \u 或 \U:Unicode 字符
  5. \:反斜杠自身
go 复制代码
var str = "go\nGo大法好"
fmt.Print(str)

如果使用``反引号,会被原样进行赋值和输出

go 复制代码
 fmt.Println(`\t go教程Go大法好`)  // \t go教程Go大法好
 fmt.Println(`\t go教程
 Go大法好`) //使用反引号 可以进行字符串换行
//反引号一般用在 需要将内容进行原样输出的时候 使用

字符串是字节的定长数组,byte 和 rune 都是字符类型,若多个字符放在一起,就组成了字符串

比如 hello ,对照 ascii 编码表,每个字母对应的编号是:104,101,108,108,111

go 复制代码
import (
    "fmt"
)

func main() {
    var mystr01 string = "hello"
    var mystr02 [5]byte = [5]byte{104, 101, 108, 108, 111}
    fmt.Printf("myStr01: %s\n", mystr01)
    fmt.Printf("myStr02: %s", mystr02)
}

思考:hello,go教程 占用几个字节

go 复制代码
package main

import (
	"fmt"
)

func main() {
   //中文三字节,字母一个字节
var myStr01 string = "hello,go教程"
fmt.Printf("mystr01: %d\n", len(myStr01))
}

5.5.1 字符串的应用

一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。

字符串所占的字节长度可以通过函数 len() 来获取,例如 len(str)。

字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[ ]内写入索引,索引从 0 开始计数:

  • 字符串 str 的第 1 个字节:str[0]
  • 第 i 个字节:str[i - 1]
  • 最后 1 个字节:str[len(str)-1]

需要注意的是,这种转换方案只对纯 ASCII 码的字符串有效。

注意:获取字符串中某个字节的地址属于非法行为,例如 &str[i]。

ASCII字符使用len()函数

Unicode字符串长度使用utf8.RuneCountInString()函数

go 复制代码
//如何计算字符串的长度
str3 := "hello"
str4 := "你好"
fmt.Println(len(str3))  // 1个字母占1个字节
fmt.Println(len(str4))  // 1个中文占3个字节,go从底层支持utf8
fmt.Println(utf8.RuneCountInString(str4)) // 2
5.5.1.1 拼接符"+"

两个字符串 s1 和 s2 可以通过 s := s1 + s2 拼接在一起。将 s2 追加到 s1 尾部并生成一个新的字符串 s。

go 复制代码
//因为编译器会在行尾自动补全分号,所以拼接字符串用的加号"+"必须放在第一行末尾。
	str := "第一部分 " +
		"第二部分"

也可以使用"+="来对字符串进行拼接:

go 复制代码
s := "hel" + "lo,"
s += "world!"
fmt.Println(s) //输出 "hello, world!"

除了使用+进行拼接,我们也可以使用WriteString()

go 复制代码
str1 := "你好,"
str2 := "go教程"
var stringBuilder bytes.Buffer
//节省内存分配,提高处理效率
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
fmt.Println(stringBuilder.String())
5.5.1.2 遍历

如果从字符串 hello go教程 中获取 该如何获取呢?

直接索引对rune类型无效,可以使用string方法转换

go 复制代码
string([]rune(str6)[0])
go 复制代码
var myStr01 string = "hello,go教程"
fmt.Println(string([]rune(myStr01)[8]))

unicode字符集使用for range进行遍历,ascii字符集可以使用for range或者for循环遍历

go 复制代码
var str1 string = "hello"
var str2 string = "hello,go教程"
// 遍历
for i :=0; i< len(str1);i++{
    fmt.Printf("ascii: %c %d\n", str1[i], str1[i])
}
for _, s := range  str1{
    fmt.Printf("unicode: %c %d\n ", s, s)
}
// 中文只能用 for range
for _, s := range  str2{
    fmt.Printf("unicode: %c %d\n ", s, s)
}

在 Go 的 for range 结构中,迭代字符串(或切片、数组、map、channel)时,返回的往往是两个值:

  1. 索引 (或键),类型通常是 int(针对切片或数组时)或适合该容器的类型(map 是键类型,channel 没有索引)。
  2. 元素值,也就是你真正想要处理的内容。
5.5.1.3 格式化
  • print : 结果写到标准输出
  • Sprint:结果会以字符串形式返回
go 复制代码
str1 := "你好,"
str2 := "go教程"
var stringBuilder bytes.Buffer
stringBuilder.WriteString(str1)
stringBuilder.WriteString(str2)
// Sprint 以字符串形式返回
result := stringBuilder.String()
fmt.Println(result)
go 复制代码
%c  单一字符
%T  动态类型
%v  本来值的输出
%+v 字段名+值打印
%d  十进制打印数字
%p  指针,十六进制
%f  浮点数
%b 二进制
%s string
5.5.1.4 查找

如何获取字符串中的某一段字符?

  • strings.Index(): 正向搜索子字符串
  • strings.LastIndex():反向搜索子字符串
go 复制代码
package main

import (
	"fmt"
	"strings"
)

func main() {
	// 查找
	tracer := "张三来了,张三bye bye"

	// 正向搜索字符串
	comma := strings.Index(tracer, ",")
	fmt.Println(",所在的位置:",comma)
	fmt.Println(tracer[comma+1:])  // 张三bye bye

	add := strings.Index(tracer, "+")
	fmt.Println("+所在的位置:",add)  // +所在的位置: -1

	pos := strings.Index(tracer[comma:], "张三")
	fmt.Println("张三,所在的位置", pos) // 张三,所在的位置 1

	fmt.Println(comma, pos, tracer[comma+pos:])  // 12 1 张三bye bye
}

5.6 类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:

go 复制代码
//类型 B 的值 = 类型 B(类型 A 的值)
valueOfTypeB = type B(valueOfTypeA)

示例:

go 复制代码
a := 5.0
b := int(a)

类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。

当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失的情况。

只有相同底层类型的变量之间可以进行相互转换(如将 int16 类型转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型):

go 复制代码
package main
import (
        "fmt"
        "math"
)
func main() {
        // 输出各数值范围
        fmt.Println("int8 range:", math.MinInt8, math.MaxInt8)
        fmt.Println("int16 range:", math.MinInt16, math.MaxInt16)
        fmt.Println("int32 range:", math.MinInt32, math.MaxInt32)
        fmt.Println("int64 range:", math.MinInt64, math.MaxInt64)
        // 初始化一个32位整型值
        var a int32 = 1047483647
        // 输出变量的十六进制形式和十进制值
        fmt.Printf("int32: 0x%x %d\n", a, a)
        // 将a变量数值转换为十六进制, 发生数值截断
        b := int16(a)
        // 输出变量的十六进制形式和十进制值
        fmt.Printf("int16: 0x%x %d\n", b, b)
        // 将常量保存为float32类型
        var c float32 = math.Pi
        // 转换为int类型, 浮点发生精度丢失
        fmt.Println(int(c))
}
go 复制代码
//结果
int8 range: -128 127
int16 range: -32768 32767
int32 range: -2147483648 2147483647
int64 range: -9223372036854775808 9223372036854775807
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759
3

根据输出结果,16 位有符号整型的范围是 -32768~32767,而变量 a 的值 1047483647 不在这个范围内。1047483647 对应的十六进制为 0x3e6f54ff,转为 int16 类型后,长度缩短一半,也就是在十六进制上砍掉一半,变成 0x54ff,对应的十进制值为 21759。

浮点数在转换为整型时,会将小数部分去掉,只保留整数部分。

5.6.1 修改字符串

Golang语言的字符串是不可变的

修改字符串时,可以将字符串转换为[]byte进行修改

[]byte和string可以通过强制类型转换

案例:将8080改为8081

go 复制代码
package main

import "fmt"

func main() {
	s1 := "localhost:8080"
	fmt.Println(s1)
	// 强制类型转换 string to byte
	strByte := []byte(s1)
	// 下标修改
	strByte[len(s1)-1] = '1'
	fmt.Println(strByte)
	// 强制类型转换 []byte to string
	s2 := string(strByte)
	fmt.Println(s2)
}
5.6.1.1 小练习

字符串替换, 比如将 "Hello, go教程Java教程" 替换为 "Hello, go教程Go教程"

思路:

  1. 找到Java所在的位置
  2. 根据Java的长度将其分为两部分
  3. 加上Go总共三部分,进行拼接
go 复制代码
package main
import (
    "fmt"
    "strings"
)
func main() {
    str := "Hello, go教程Java教程"
    source := "Java"
    target := "Go"
    goIndex := strings.Index(str, source)
    if goIndex == -1 {
       return
    }
    sourceLen := len(source)
    str = str[:goIndex] + target + str[goIndex+sourceLen:]
    fmt.Printf("result: %s\n", str)
}

5.6.2 字符串与其他数据类型的转换

  1. 整数 与 字符串

    go 复制代码
    // 字符串与其他类型的转换
    // str 转 int
    newStr1 := "1"
    intValue, _ := strconv.Atoi(newStr1)
    fmt.Printf("%T,%d\n", intValue, intValue)  // int,1
    
    // int 转 str
    intValue2 := 1
    strValue := strconv.Itoa(intValue2)
    fmt.Printf("%T, %s\n", strValue, strValue)
  2. 浮点数 与字符串

    go 复制代码
    // str 转  float
    string3 := "3.1415926"
    f, _ := strconv.ParseFloat(string3, 32)
    fmt.Printf("%T, %f\n", f, f) // float64, 3.141593
    //float 转 string
    floatValue := 3.1415926
    //4个参数,1:要转换的浮点数 2. 格式标记(b、e、E、f、g、G)
    //3. 精度 4. 指定浮点类型(32:float32、64:float64)
    // 格式标记:
    // 'b' (-ddddp±ddd,二进制指数)
    // 'e' (-d.dddde±dd,十进制指数)
    // 'E' (-d.ddddE±dd,十进制指数)
    // 'f' (-ddd.dddd,没有指数)
    // 'g' ('e':大指数,'f':其它情况)
    // 'G' ('E':大指数,'f':其它情况)
    //
    // 如果格式标记为 'e','E'和'f',则 prec 表示小数点后的数字位数
    // 如果格式标记为 'g','G',则 prec 表示总的数字位数(整数部分+小数部分)
    formatFloat := strconv.FormatFloat(floatValue, 'f', 2, 64)
    fmt.Printf("%T,%s", formatFloat, formatFloat)
5.6.2.1 文字游戏
  1. :开发一款游戏

​ //捕获标准输入,并转换为字符串

​ reader := bufio.NewReader(os.Stdin)

​ input, err := reader.ReadString('\n')

​ if err != nil {

​ //如果有错误 退出

​ panic(err)

​ }

需求:能打怪升级

go 复制代码
func main() {
    fmt.Println("请输入你的角色名字")
    //捕获标准输入,并转换为字符串
    reader := bufio.NewReader(os.Stdin)
    input, err := reader.ReadString('\n')
    if err != nil {
       panic(err)
    }
    //删除最后的\n
    name := input[:len(input)-2]
    fmt.Printf("角色创建成功,%s,欢迎你来到张三游戏,目前角色等级%d \n", name, level)
    s := `你遇到了一个怪物,请选择是战斗还是逃跑?
    1.战斗
    2.逃跑
    exit.退出`
    fmt.Printf("%s \n", s)
    for {
       input, err := reader.ReadString('\n')
       if err != nil {
          panic(err)
       }
       selector := input[:len(input)-2]
       switch selector {
       case "1":
          ex += 10
          fmt.Printf("杀死了怪物,获得了%d经验 \n", ex)
          computeLevel()
          fmt.Printf("您现在的等级为%d \n", level)
       case "2":
          fmt.Printf("你选择了逃跑\n")
          fmt.Printf("%s \n", s)
       case "exit":
          fmt.Println("你退出了游戏")
          //退出
          os.Exit(1)
       default:
          fmt.Println("你的输入我不认识,请重新输入")
       }
    }
}

func computeLevel() {
    if ex < 20 {
       level = 1
    } else if ex < 40 {
       level = 2
    } else if ex < 200 {
       level = 3
    } else {
       level = 4
    }
}

5.7 类型别名

类型别名是 Go 1.9 版本添加的新功能,主要用于解决代码升级、迁移中存在的类型兼容性问题。

格式:

go 复制代码
//TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型,就像一个孩子小时候有小名、乳名,上学后用学名,英语老师又会给他起英文名,但这些名字都指的是他本人。
type TypeAlias = Type

还有一种是类型定义:

go 复制代码
//定义Name为Type类型 ,定义之后 Name为一种新的类型
type Name Type

类型别名与类型定义表面上看只有一个等号的差异,那么它们之间实际的区别有哪些呢?

go 复制代码
package main
import (
    "fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
    // 将a声明为NewInt类型
    var a NewInt
    // 查看a的类型名 main.NewInt
    fmt.Printf("a type: %T\n", a)
    // 将a2声明为IntAlias类型
    var a2 IntAlias
    // 查看a2的类型名 int 
    //IntAlias 类型只会在代码中存在,编译完成时,不会有 IntAlias 类型。
    fmt.Printf("a2 type: %T\n", a2)
}

6 常量

Go语言中的常量使用关键字const定义,用于存储不会改变的数据,常量是在编译时被创建的,即使定义在函数内部也是如此,并且只能是布尔型数字型(整数型、浮点型和复数)和字符串型

由于编译时的限制,定义常量的表达式必须为能被编译器求值的常量表达式。

声明格式:

go 复制代码
const name [type] = value

例如:

go 复制代码
const pi = 3.14159

type可以省略

和变量声明一样,可以批量声明多个常量:

go 复制代码
const (
    e  = 2.7182818
    pi = 3.1415926
)

所有常量的运算都可以在编译期完成,这样不仅可以减少运行时的工作,也方便其他代码的编译优化,当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。

常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用都是返回常量结果:len、cap、real、imag、complex 和 unsafe.Sizeof。

因为它们的值是在编译期就确定的,因此常量可以是构成类型的一部分

如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式,对应的常量类型也是一样的。例如:

go 复制代码
const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // "1 1 2 2"

6.1 iota 常量生成器

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。

在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加1

比如,定义星期日到星期六,从0-6

go 复制代码
const (
    Sunday  = iota //0
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday  //6
)

7 指针

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

受益于这样的约束和拆分,Go语言的指针类型变量即拥有指针高效访问的特点,又不会发生指针偏移,从而避免了非法修改关键性数据的问题。

同时,垃圾回收也比较容易对不会发生偏移的指针进行检索和回收。

切片比原始指针具备更强大的特性,而且更为安全。

切片在发生越界时,运行时会报出宕机,并打出堆栈,而原始指针只会崩溃。

7.1 如何理解指针

var a int = 10

如果用大白话来解释上述语句:

在内存中开辟了一片空间,空间内存放着数值10,这片空间在整个内存当中,有一个唯一的地址,用来进行标识,指向这个地址的变量就称为指针

如果用类比的说明:

内存比作酒店,每个房间就是一块内存,上述代码表示为:定了一间房间a,让10住进了房间,房间有一个门牌号px,这个px就是房间的地址,房卡可以理解为就是指针,指向这个地址。

一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。

当一个指针被定义后没有分配到任何变量时,它的默认值为 nil

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。

Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:

go 复制代码
//其中 v 代表被取地址的变量,变量 v 的地址使用变量 ptr 进行接收,ptr 的类型为*T,称做 T 的指针类型,*代表指针。
ptr := &v    // v 的类型为 T
go 复制代码
package main
import (
    "fmt"
)
func main() {
    var cat int = 1
    var str string = "go教程"
    fmt.Printf("%p %p", &cat, &str)
}

输出

go 复制代码
0xc0000aa058 0xc000088220

变量、指针和地址三者的关系是,每个变量都拥有地址,指针的值就是地址

当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值

go 复制代码
var room int = 10  // room房间 里面放的 变量10
var ptr = &room  // 门牌号px  指针  0xc00000a0a8

fmt.Printf("%p\n", &room)  // 变量的内存地址 0xc00000a0a8

fmt.Printf("%T, %p\n", ptr, ptr)  // *int, 0xc00000a0a8

fmt.Println("指针地址",ptr)   // 0xc00000a0a8
fmt.Println("指针地址代表的值", *ptr)  // 10

取地址操作符&和取值操作符*是一对互补操作符,&取出地址,*根据地址取出地址指向的值

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:

  • 对变量进行取地址操作使用&操作符,可以获得这个变量的指针变量。
  • 指针变量的值是指针地址。
  • 对指针变量进行取值操作使用*操作符,可以获得指针变量指向的原变量的值。

7.2 使用指针修改值

通过指针不仅可以取值,也可以修改值。

go 复制代码
package main

func main(){
    // 利用指针修改值
	var num = 10
	modifyFromPoint(num)
	fmt.Println("未使用指针,方法外",num)

	var num2 = 22
	newModifyFromPoint(&num2)  // 传入指针
	fmt.Println("使用指针 方法外",num2)
}

func modifyFromPoint(num int)  {
	// 未使用指针
	num = 10000
	fmt.Println("未使用指针,方法内:",num)
}

func newModifyFromPoint(ptr *int)  {
	// 使用指针
	*ptr = 1000   // 修改指针地址指向的值
	fmt.Println("使用指针,方法内:",*ptr)
}

7.3 创建指针的另一种方法

Go语言还提供了另外一种方法来创建指针变量,格式如下:

go 复制代码
new(类型)
go 复制代码
str := new(string)
*str = "go教程Go语言教程"
fmt.Println(*str)

new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。

7.4 指针小案例

获取命令行的输入信息

Go语言内置的 flag 包实现了对命令行参数的解析,flag 包使得开发命令行工具更为简单。

go 复制代码
package main
// 导入系统包
import (
    "flag"
    "fmt"
)
// 定义命令行参数
var mode = flag.String("mode", "", "fast模式能让程序运行的更快")
func main() {
    // 解析命令行参数
    flag.Parse()
    fmt.Printf("运行模式为:%s", *mode)
}

在命令行中运行

shell 复制代码
PS F:\Code\Golang\TuLing\workPath\learing02> go run .\main.go --mode=fast
运行模式为:fast

8 注释

Go语言的注释主要分成两类,分别是单行注释和多行注释。

  • 单行注释简称行注释,是最常见的注释形式,可以在任何地方使用以//开头的单行注释;
  • 多行注释简称块注释,以/*开头,并以*/结尾,且不可以嵌套使用,多行注释一般用于包的文档描述或注释成块的代码片段。

单行注释的格式如下所示

go 复制代码
//单行注释

多行注释的格式如下所示

go 复制代码
/*
第一行注释
第二行注释
...
*/

每一个包都应该有相关注释,在使用 package 语句声明包名之前添加相应的注释,用来对包的功能及作用进行简要说明。

同时,在 package 语句之前的注释内容将被默认认为是这个包的文档说明。一个包可以分散在多个文件中,但是只需要对其中一个进行注释说明即可。

9 关键字和标识符

关键字

关键字即是被Go语言赋予了特殊含义的单词,也可以称为保留字。

Go语言中的关键字一共有 25 个:

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

之所以刻意地将Go语言中的关键字保持的这么少,是为了简化在编译过程中的代码解析。

和其它语言一样,关键字不能够作标识符使用。

标识符

标识符是指Go语言对各种变量、方法、函数等命名时使用的字符序列,标识符由若干个字母、下划线_、和数字组成,且第一个字符必须是字母。

下划线_是一个特殊的标识符,称为空白标识符

标识符的命名需要遵守以下规则:

  • 由 26 个英文字母、0~9、_组成;
  • 不能以数字开头,例如 var 1num int 是错误的;
  • Go语言中严格区分大小写;
  • 标识符不能包含空格;
  • 不能以系统保留关键字作为标识符,比如 break,if 等等。

命名标识符时还需要注意以下几点:

  • 标识符的命名要尽量采取简短且有意义;
  • 不能和标准库中的包名重复;
  • 为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;

在Go语言中还存在着一些特殊的标识符,叫做预定义标识符,如下表所示:

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

预定义标识符一共有 36 个,主要包含Go语言中的基础数据类型和内置函数,这些预定义标识符也不可以当做标识符来使用。

10 运算符优先级

所谓优先级,就是当多个运算符出现在同一个表达式中时,先执行哪个运算符。

Go语言有几十种运算符,被分成十几个级别,有的运算符优先级不同,有的运算符优先级相同,请看下表。

优先级 分类 运算符 结合性
1 逗号运算符 , 从左到右
2 赋值运算符 =、+=、-=、*=、/=、 %=、 >=、 <<=、&=、^=、|= 从右到左
3 逻辑或 || 从左到右
4 逻辑与 && 从左到右
5 按位或 | 从左到右
6 按位异或 ^ 从左到右
7 按位与 & 从左到右
8 相等/不等 ==、!= 从左到右
9 关系运算符 <、<=、>、>= 从左到右
10 位移运算符 <<、>> 从左到右
11 加法/减法 +、- 从左到右
12 乘法/除法/取余 *(乘号)、/、% 从左到右
13 单目运算符 !、*(指针)、& 、++、--、+(正号)、-(负号) 从右到左
14 后缀运算符 ( )、[ ]、-> 从左到右

注意:优先级值越大,表示优先级越高。

一下子记住所有运算符的优先级并不容易,还好Go语言中大部分运算符的优先级和数学中是一样的,大家在以后的编程过程中也会逐渐熟悉起来。如果实在搞不清,可以加括号,就像下面这样:

go 复制代码
d := a + (b * c)

括号的优先级是最高的,括号中的表达式会优先执行,这样各个运算符的执行顺序就一目了然了。

相关推荐
黑客老李12 分钟前
BaseCTF scxml 详解
开发语言·网络·数据库·python·sql·安全
编程|诗人14 分钟前
Ruby语言的数据库编程
开发语言·后端·golang
SyntaxSage15 分钟前
Ruby语言的学习路线
开发语言·后端·golang
云端 架构师15 分钟前
Ruby语言的并发编程
开发语言·后端·golang
DevOpsDojo16 分钟前
Ruby语言的字符串处理
开发语言·后端·golang
Code花园26 分钟前
Scala语言的循环实现
开发语言·后端·golang
wjs202431 分钟前
AngularJS HTML DOM
开发语言
易雪寒1 小时前
Java大厂面试题之10种分布式ID的生成方案
java·开发语言·分布式
树不懒1 小时前
【js引擎】quickjs 中的两个 proto
开发语言·javascript·原型模式