1. 注释

1.1 多行注释

1.1.1 方式一(不推荐使用)

package main

/* 多行注释
  test函数的作用
  参数a类型和作用
  参数b类型和作用
  参数c类型和作用
*/
func test1(a int, b string, c bool){

}

Go 1.19.4 语法基础_GO


1.1.2 方式二(推荐)

go的源码库中也是使用这种多行注释方式

package main

// test函数的作用,
// 参数a类型和作用,
// 参数b类型和作用,
// 参数c类型和作用.
func test2(a int, b string, c bool){

}

Go 1.19.4 语法基础_GO_02


1.2 单行注释

package main

import "fmt"

func main() {
	// 单行注释
	fmt.Println("fmt") // 打印什么的
}

1.3 TODO 提示功能没有完成

1.3.1 安装插件 todo tree

Go 1.19.4 语法基础_GO_03


1.3.2 最终效果

package main

import "fmt"

func main() {
	// TODO: 还有一个功能待完成
	fmt.Println("fmt")
}

Go 1.19.4 语法基础_GO_04

类似的还有:

// NOTE: 请注意

// Deprecated: 告知已经过期,建议不要使用。未来某个版本可能移除

不过todo比较常用。

1.4 注释总结

(1)函数、结构体等习惯把注释写在函数或结构体上面

(2)包注释会写在package之上


2. 命名规范

(1)标识符采用CamelCase驼峰命名法

  • 如果只在包内可用,就采用小驼峰命名(userName)
  • 如果要在包外可见(另一个包中可见),就采用大驼峰命名(UserName),也被称为包级别的全局变量或者导出变量。
  • 大小驼峰在包内外都能用,但是如果要在包外可见,就必须用大驼峰。

(2)简单循环变量可以使用i、j、k、v等,就是单独的字母也能使用。

(3)条件变量、循环变量可以是单个字母或单个单词,Go倾向于使用单个字母。

(4)常量驼峰命名即可

  • 在其他语言中,常量多使用全大写加下划线的命名方式,Go语言没有这个要求。
  • 对约定俗成的全大写,例如PI。


(5)函数/方法的参数、返回值应是单个单词或单个字母。


(6)函数可以是多个单词命名。


(7)类型可以是多个单词命名。


(8)方法由于调用时会绑定类型,所以可以考虑使用单个单词。


(9)包以小写单个单词命名,包名应该和导入路径的最后一段路径保持一致。


(10)接口优先采用单个单词命名,一般加er后缀。Go语言推荐尽量定义小接口(就是接口中的功能尽可能的少),最后用若干个小接口来组成一个大接口。


3. 关键字

官网:https://golang.google.cn/ref/spec

// 所谓的关键字就是go程序用的(语言保留字),我们开发过程中不能用关键字命名。
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


4. 预定义标识符

官网:https://golang.google.cn/ref/spec#Predeclared_identifiers

// 预定义标识符可以用,但是不建议使用,会出问题。
Types:
 any bool byte comparable
 complex64 complex128 error float32 float64
 int int8 int16 int32 int64 rune string
 uint uint8 uint16 uint32 uint64 uintptr

Constants:
 true false iota

Zero value:
 nil

Functions:
 append cap close complex copy delete imag len
 make new panic print println real recover

5. 标识符

在编程中,标识符(Identifier)是用来表示变量、常量、函数、类型、接口或其他用户定义的实体的名称。标识符可以被用于命名各种程序元素,以便在代码中引用和识别它们。


在Go语言中,标识符的命名规则如下:

  • 一个名字,本质上是个字符串,用来指代一个值。
  • 只能是大小写字母、数字、下划线,也可以是Unicode字符
  • 不能以数字开头
  • 不能是Go语言的关键字
  • 尽量不要使用“预定义标识符”,否则后果难料
  • 大小写敏感


标识符建议:

  • 不要使用中文
  • 非必要不要使用拼音
  • 尽量遵守上面的命名规范,或形成一套行之有效的命名规则


总结:

  • 标识符是专门给开发人员用的,用来指代内存中的一个值,如a = 123。
  • 标识符在编译过后,就看不到了,因为被替换成了内存地址,这个内存地址中,存放着对应的值,如123。


5.1 字面常量

字面常量是值。

5.1.1 字面常量含义

在Go语言中,字面常量(Literal Constants)是直接写在代码中的固定值,无需使用变量声明。对于整数、浮点数、布尔值、字符串、空值等,都可以作为字面常量。


所谓字面的意思,就是你一看就知道这是什么类型的数据,它是值,但不是标识符。

但你不可以去修改这个值,因为它是常量,只有变量才可以修改。

对于能不能修改,取决于你如何定义标识符,如下:

如a := 100,这个100就是一个数值类型的字面常量,而a只是一个标识符,同时a也是一个变量,那么此时,100在程序运行过程中是可变的。


又如:const b = 200,这里的200依然是一个数值类型的字面常量,b也依然是标识符,但同时,由于使用了const,所以b也是一个常量,所以这里的200,在程序运行过程中不可变。


5.1.2 字面常量示例

/* 数值常量
// 数值类的字面常量
100
0x6162 0x61_62_63
3.14
3.14e2
3.14E-2

// 字符(rune)类的字面常量
// 字符不管是多少个组成,如'xxx',都视为一个字符
'测'
'\u6d4b'
'\x31'
'1'
'\n'
*/数值常量

/* 字面常量
// 字符串类的字面常量
"abc" "\x61b\x63"
"测试" "\u6d4b试"
"\n"
*/字面常量


// 其他类型的字面常量
tue
false
iota

5.2 常量

5.2.1 什么是常量

什么是常量?

首先它是一个标识符,这个标识符有一个值,但这个值在程序运行过程中是不可改变的,如const a = 100,这个a就是常量,100为常量值。


是在其他语言中,指的是元素地址不可变,内容可变。

但在go中,要求更加严格,要求的是内容都不能变。


常量:使用const定义一个标识符,它所对应的值,不允许被修改。

对常量并不要求全大写加下划线的命名规则。


在定义常量时,它的值只能是字面常量,如果是其他值,就直接报错了

5.2.2 常量示例

5.2.2.1 单个常量定义
const a int = 100 // 指定类型定义并赋值
5.2.2.2 多个(批量)常量定义
const ( // “无类型常量untyped constant”定义,推荐
	b = "abc" // 不写数据类型时,go会根据值自动判断常量是什么类型。
	c = 12.3
	d = 'T'
)
5.2.2.3 错误的定义方式
// 错误,const定义常量时,必须在定义时赋值,并且之后不能改变值,换一种说法就是常量在定义时,必须被初始化,=赋值就是初始化。
const a

// 错误,数组的容器内容{1, 2}会变化,凡是不能在编译期间明确地确定下来的,在go中是不被允许被定义为常量的。
const c = [2]int{1, 2}

5.3 iota

5.3.1 iota介绍

在Go语言中,iota 是一个被预定义的无类型整数常量,它用于在常量声明中生成一系列递增的值。


在每个const关键字出现时,iota会被重置为0(但它本身也是从0开始),然后在每个连续的常量声明中逐步递增。

5.3.2 单iota演示

package main

import "fmt"

func main() {
	const a = iota // 0,因为有const
	const b = iota // 0,因为有const
	fmt.Println(a, b)
}
=================调试结果=================
0 0

5.3.3 多iota演示

批量iota操作的时候,才会出现递增效果,0、1、2……

func main() {
	const ( // 多iota递增
		c = iota // iota=0
		d        // iota=1
		e        // iota=2
		_        // 下划线为特殊标识符,在go中可以用来做标识符,但是不能使用它,如print _,会报错。iota=3
		_        // 也可以叫空白标识符或匿名变量。iota=4
		f        // iota=5
		g = iota // iota=6
		h        // iota=7
	)
	fmt.Println(c, d, e, f, g, h)
}
=================调试结果=================
0 1 2 5 6 7

代码变化一

package main

import "fmt"

func main() {
	const ( // 多iota递增
		c = iota
		d
		e
		_
		_
		f
		// 下面开始变化
		g = iota + 10 // iota=6+10=16
		h             // iota=7,但是会复用上面iota+10的公式,也就是iota=7+10=17
	)
	fmt.Println(c, d, e, f, g, h)
}
=================调试结果=================
0 1 2 5 16 17

代码变化二

package main

import "fmt"

func main() {
	const ( // 多iota递增
		c = 5 // 改变这里,会发现直到g,都是5,因为一个多iota一旦被定义,不管位置在哪儿,都会从第一行开始
		d
		e
		_
		_
		f
		g = iota + 10
		h
		i = 20
		j
		k = iota
		l
	)
	fmt.Println(c, d, e, f, g, h, i, j, k, l)
}
=================调试结果=================
5 5 5 5 16 17 20 20 10 11

_下划线 是空白标识符

下划线和其他标识符使用方式一样,但它不会分配内存,不占名词空间

为匿名变量赋值,其值会被抛弃,因此,后续代码中不能使用匿名变量的值,也不能使用匿名变量为其他变量赋值。

说白了就是只能用_占位,不能调用它。


5.3.4 iota应用场景

枚举类型:iota可以用于创建枚举类型的常量集。例如,可以使用iota来定义一周中每一天的常量。

const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

5.4 变量

5.4.1 变量介绍

变量:赋值后,可以改变值的标识符,但是对于go这种强类型语言来说,不能换数据类型,比如a = 1,然后下面又a = "a"。

建议采用驼峰命名法。

定义方式:

(1)长格式定义

var a = 100


(2)短格式定义

a := 100


5.4.2 变量定义示例

5.4.2.1 单个变量定义
package main

import (
	"fmt"
)

func main() {
	//var a // 错误的声明方式。声明变量时,必须要指定数据类型

	var a int // 正确的声明方式。没有指定具体的变量值时,会自动初始化一个零值(整数0),因为go中不允许有空值的变量。
	fmt.Println(a)

	var b = 200 // 正确的声明方式。没有指定具体的数据类型时,声明(var b)+初始化(b = 200)后,go会自动推导出值的数据类型
	fmt.Println(b)
    
}
=================调试结果=================
0
200
5.4.2.2 多个变量定义
func main() {
	//var a int, b int // 错误的声明方式

	var a, b int // 正确的声明方式。批量同类型合并
	fmt.Println(a, b)

	var ( // 正确的声明方式
		c int = 300
		d int
	)
	fmt.Println(c, d)

	var g, h int = 100, 200 // 正确的声明方式
	fmt.Println(g, h)

	var ( // 正确的声明方式
		m int
		n int
		t string = "abc"
	)
	m, n = 300, 400
	fmt.Println(m, n, t)

	m, n = n, m // 交换写法。但是要注意Go语言的多重赋值是同时执行的
	fmt.Println(m, n)
    
    //var a = 100 // 错误的声明方式,因为上面a已经被var声明过了
    a = 100 // 正确的声明方式
}
=================调试结果=================
0 0
300 0
100 200
300 400 abc
400 300
5.4.2.3 多变量定义的注意事项

Go语言的多重赋值是同时执行的

package main

import "fmt"

func main() {
	var i, j, m, n int
	i, j, m, n = 1, i+1, j+1, m+1
	fmt.Println(i, j, m, n) // 1 1 1 1
}
===================
1 1 1 1
5.4.2.4 短格式变量定义

推荐使用这种方式来声明变量,但是它只能在函数内部使用,不能用来全局变量。

使用 := 定义变量并立即初始化

只能用在函数中,不能用来定义全局变量、不能提供数据类型,具体的数据类型由编译器来推断

func main() {
	a := 100 // 正确的声明方式。:=,表示声明变量+定义变量
	//a := 200 // 不能这样!因为上面已经声明和定义过了,不能重复声明定义。
	a = 300 // 可以这样,单纯的赋值。
	fmt.Println(a)
}
=============
300

5.5 零值

变量已经被声明,但是未被显式初始化(var a int),这个变量将会被设置为零值。

var a int = 100

声明:var a int

初始化:= 100


其它语言中,只声明未初始化的变量误用非常危险,但是,Go语言却坚持“零值可用”理念。

  • int为0
  • float为0.0
  • bool为false
  • string为空串""(注意是双引号)
  • 指针类型为nil
  • 其它类型数据零值,学到再说


5.6 变量作用域

5.6.1 包级标识符(全局)

在Go语言中,在.go文件中的顶层代码中(函数体外部),定义的标识符称为包级标识符。

全局变量可以在整个包或者包外被使用,如果首字母大写,可在包外可见。如果首字母小写,则包内可见。

// 无类型常量定义(包外可见)
var A = 20   // int
var B = 3.14 // float64

// 无类型常量定义(包内可见)
var a = 20   // int
var b = 3.14 // float64
// 指定类型
var a int32 = 20
var b float32 = 3.14

// 延迟初始化需要指定类型,用零值先初始化,因为不给类型,不知道用什么类型的零值
// 有相同关系的声明可以使用同一批定义
var (
 name string
 age  int
)

使用建议:

(1)顶层代码中定义包级标识符

  • 首字母大写作为包导出标识符,首字母小写作为包内可见标识符。
  • const定义包级常量,必须在声明时初始化
  • var定义包级变量,可以指定类型,也可以使用无类型常量定义,延迟赋值必须指定类型,不然没法确定零值。


(2)有相关关系的,可以批量定义在一起


(3)一般声明时,还是考虑“就近原则”,尽量靠近第一次使用的地方声明


(4)不能使用短格式定义

5.6.2 局部标识符

定义在函数体内部,包括main函数,这些标识符就是局部标识符。

使用建议:

  • 在函数中定义的标识符
  • const定义局部常量
  • var定义局部变量,可以指定类型,也可以使用无类型常量定义,延迟赋值必须指定类型,不然没法确定零值。
  • 有相关关系的,可以批量定义在一起。
  • 在函数内,直接赋值的变量多采用短格式定义。

5.6.3 形式参数

函数定义中的变量称为形式参数。

func sum(a, b int) int { // 这里的ab就是形式参数
 xxx
}

6. 布尔型

类型bool,定义了2个预定义常量,分别是true、false。

在其他语言中,布尔型可以和其他类型的数据进行运算,但是在go中,不可以的,bool就是bool。

6.1 布尔表达式符号

逻辑与:&&

逻辑或:||

逻辑非:!

运算符:==、!=、>、<、>=、<=

7 . 数值型

官网文档:https://golang.google.cn/ref/spec#Numeric_types

7.1 整型

  • 长度不同有符号::int8(1个字节)、int16(C语言short,2个字节)、int32(4个字节)、int64(C语言long,8个字节)
  • 长度不同无符号:uint8(1个字节)、uint16(2个字节)、uint32(4个字节)、uint64 (个字节)

     byte类型,它是uint8的别名

  • 自动匹配平台:int、uint


int类型它至少占用32位,但一定注意它不等同于int32,不是int32的别名。要看CPU,32位 就是4字节,64位就是8字节。但是也不是说int是8字节64位,就等同于int64,它们依然是不同类型!

7.1.1 什么是有符号和无符号

这里用int8(1字节)举例

首先这里介绍下进制,如在10进制中,是看不到10的,因为计数是从0开始,到9,当计数超过9时,需要使用一个1和一个0来表示10。同理,其他进制也相同。


那么如何理解有无符号呢?

首先说下有符号,这里先以二进制为例:

如00000001,首先最左边属于最高位,最右边属于最低位,1byte=8bits,1个字节,就是8个位,每个位置上只有2种可能,也就是0或1,这就是二进制。

二进制中,用最高位的0或1来表示正负符号,其中0为正,1为负。那么如下:

10000001,这个二进制的最高位就是-1(负符号)。


那么无符号呢?

和有符号相反,最高位不表示符号。

如00000001,就是1,因为它没有符号位,所有位都用于表示非负整数。

为啥是1?

其实这里是涉及到了进制转换计算,首先在进制中,是由权重一说的。

如二进制,它的权重底数是2,权重从右到左依次是:1、2、4、8、16、32等等以此类推。

那么二进制00000001转换成十进制方式就是如下图:

Go 1.19.4 语法基础_GO_05

7.1.2 有无符号整型范围

7.1.2.1 有符号

计算方式:

取值范围 = -(2^(n-1)) 到 (2^(n-1))-1,其中n为类型的位数:如int8,n就是8。

  • -(2^(8-1)) = -128
  • 2^(8-1)-1 = 2^7 = 128 -1 = 127

数据类型

位数

占用内存空间

取值范围

int8

8

1字节

-128 到 127

int16

16

2字节

-32,768 到 32,767

int32

(别名rune)

32

4字节

-2,147,483,648 到 2,147,483,647

int64

64

8字节

-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807


7.1.2.2 无符号

计算方式:

取值范围=0~2^n-1,n为位数。

数据类型

位数

占用存储空间

取值范围

uint8

(别名byte)

8

1字节

0 到 255

uint16

16

2字节

0 到 65,535

uint32

32

4字节

0 到 4,294,967,295

uint64

64

8字节

0 到 18,446,744,073,709,551,615

7.1.3 Go中的进制前缀表示法

前缀对于编译器解析数字文字非常重要。

如果没有前缀,编译器会将数字解释为十进制数。

进制类型

前缀

二进制

0b(b可以大写可小写)

八进制

0或0o(可以省略,但是不要,避免歧义,o可以大写也可以小写)

十进制

默认无前缀

十六进制

0x(X可以大写也可以小写)

十六进制字符

'\x'

例如:

  • 二进制数:0b1010
  • 八进制数:0100o10
  • 十进制数:10
  • 十六进制数:0x10

7.1.4 实际代码中的运用

package main

import "fmt"

func main() {
	a := 0x20 // 0x为16进制,表示20是个16进制类型
	fmt.Println(a) // 这里Println会把16进制自动转换为10进制并输出结果
}
=================调试结果=================
32

7.1.5 查看对应值的类型(格式化输出)

package main

import "fmt"

func main() {
	a := 0x20
	fmt.Println(a)

	b := 0b100000
	fmt.Println(b) // Println= print line,打印完后换行

	var c = 40
	d := a + b + c
	// Printf=print format,格式化输出,不换行。
	// %T对应第一个d,%d对应第二个d。
	// %T表示type类型。%d表示digital数字。
	// 也就是把d的数据类型和对应的值打印出来。
	fmt.Printf("%T %d", d, d)
    fmt.Printf("%T %v", d, d) // 当不能确定值的类型时,可以使用%v,v表示value,就是一个占位符,适用于任何场景。
}
=================调试结果=================
32
32
int 104
int 104

7.1.6 Go不支持跨类型计算说明

package main

import "fmt"

func main() {
	a := 100 // 这种不指定数据类型的,自动识别为int,int会根据当前的系统类型,来使用32位还是64位
	var b int64 = 100 // 这里就指明了int64,但是注意,假设上面的int自动识别为int64也和这里的int64不同。
	// a + b // 这里可以使用a+b,然后鼠标移到上面,会看到一个报错:invalid operation: a + b (mismatched types int and int64)compilerMismatchedTypes,
	// 意思是a是int,b是int64,不同数据类型不能相加。
    // 但是可以通过强制类型转换,来实现上面的需求。

}

7.1.7 强制类型转换

7.1.7.1 int和长度不同有符号互转
package main

import "fmt"

func main() {
	a := 100                // int
	var b int64 = 100       // int64
	fmt.Println(a + int(b)) // 直接把b的int64转换成了int
    fmt.Println(int64(a) + b) // 这样也可以,只要数据类型相同就行
}
=================调试结果=================
200
200
7.1.7.2 int转string

int转string会有一个黄色下划线警告提示:大概意思是说把一个int转成了字符串。不影响代码运行。

当把int类型强制转换为string时,Go 语言会将该整数解释为一个 ASCII 码值,并返回相应的字符。

package main

import "fmt"

func main() {
	var a = 50
    fmt.Printf("%T %[1]v\n", string(a)) // %v是比较万能的数据类型显示,字符串的类型为%S。
    // %[1]v,表示的是索引:"%T索引默认为1,对应string(a),%v默认对应第二个值,但是这里并没有,所以修正索引为1
}
=================调试结果=================
string 2 // 当把int类型强制转换为string时,Go 语言会将该整数解释为一个 ASCII 码值,并返回相应的字符。
7.1.7.3 int转其他类型
package main

import "fmt"

func main() {
	var a = 50
	fmt.Printf("%T %[1]v\n%T %[2]v\n%T %[3]f\n", string(a), rune(a), float32(a))
}
=================调试结果=================
package main

import "fmt"

func main() {
	var a = 50
	fmt.Printf("%T %[1]v\n%T %[2]v\n%T %[3]f\n", string(a), rune(a), float32(a))
}
=================调试结果=================
string 2 // 字符串,实际应该是"2",但是从int转到str显示的是ascll码值
int32 50 // 字符,实际应该是'50'
float32 50.000000 // 浮点数

Go 1.19.4 语法基础_GO_06

7.1.8 为什么要分有符号和无符号及自适应

首先说说自适应,自适应int和uint,纯粹是为了方便,当内存充足时,可以使用该方式,也是工作中大多数时使用的方式。


然后是有无符号,需要精细使用内存时,就要根据实际情况来选择不同的有无符号整型。


实际工作中,优先考虑的是程序运行速度快、无BUG,其次才是内存占用优化。

7.2 字符和整数

7.2.1 什么是字符

字符是在计算机系统中表示单个书写符号(例如字母、数字或符号)的基本单位。它是一个抽象概念,可以由不同的字符编码(例如 ASCII、Unicode)表示。


ASCII范围:0-127

Unicode范围:100万字符以上


字符''使用Unicode编码,并且Unicode是包含ASCII码的。


在Go中,字符表达,必须使用单引号引住一个字符,如:'a'、'1'、'@'等。

但是在计算机中,字符也可以用数字来表示,如ASCII码表。

ASCII码表中的内容,是某个数字映射后的某个字符,那到底是数字还是字符呢?取决于我们编程时如何使用。

比如97,我们可以单纯的把它看成是一个整型数字,但是如果提前限定它的类型为string字符类型,那么此时对照ASCII码表,97对应的字符就是'a',这个时候实际上就是操作的这个字符'a',而不是97.


计算机种只有二进制的数据,到底要怎么展示出来,取决于我们定义为什么类型的数据。

type byte = uint8 // byte定义为uint8的别名,1个字节
type rune = int32 // rune定义为int32的别名,4个字节,可以是Unicode字符

type myint int // 这种是定义新类型,不是定义别名
package main

import "fmt"

func main() {
	var a = '测' // 由于是中文,所以会先去查询Unicode表,然后返回对应的值27979
	fmt.Printf("%T %[1]v %[1]c\n", a)

	b := '2'                          // 将字符 '2' 的 ASCII 码(即二进制00110010=十进制50)赋值给变量 b。
	fmt.Printf("%T %[1]v %[1]c\n", b) // %c表示打印一个字符。此时2在rune类型下就应该是2,虽然rune就是int32
	// 换句话说,就是内存中'2'=50 int32,但只要我们打印的时候,把它当成字符,那输出出来就是字符'2'。
	// 内存中的数据到底应该是什么类型,取决于我们赋予它什么样的类型。

	// 总结:如果b := 'value',这个'value'字符字面量,那默认就是rune也就是int32,4字节.

	// 但实际上,存储50使用无符号1字节就可以了,如下:
	var c byte = '2' // 定义类型只能用var,不能用:=
	fmt.Printf("%T %[1]d %[1]c\n", c)
}
=================调试结果=================
int32 27979 测
int32 50 2
uint8 50 2

特别注意:字符串在内存中使用utf-8,rune输出是unicode。

7.3 浮点数

float32:最大范围约为3.4e38,通过math.MaxFloat32查看

float64:最大范围约为1.8e308,通过math.MaxFloat64查看

打印格式化符常用%f

package main

import "fmt"

func main() {
	f := 3.1415 // 默认类型:float64
	fmt.Printf("%T %[1]v %[1]f", f) // v:默认格式。f:浮点数,默认小数点后六位
}
=================调试结果=================
float64 3.1415 3.141500

7.3.1 浮点数格式化打印

package main

import "fmt"

func main() {
	f := 3.1415
	fmt.Printf("|%3f|\n", f)     // 打印宽度为3,但是由于f的值长度已经超过了3,所以看着没有变化
	fmt.Printf("|%20f|\n", f)    // 打印宽度为20,其中f占8个位置,剩下的12个位置在左边
	fmt.Printf("|%-20f|\n", f)   // 加个-,就表示左对齐,默认为右对齐
	fmt.Printf("|%-20.3f|\n", f) // 左对齐,并显示小数点后3位,并且会自动四舍五入
	fmt.Printf("|%.3f|\n", f)    // 只显示小数点后3位,没有对齐要求,并且会自动四舍五入
}
=================调试结果=================
|3.141500|
|            3.141500|
|3.141500            |
|3.142               |
|3.142|

7.3.2 浮点型与无符号整数字面量运算

前面说过,go中不同类型的数据不能互相运算,但是,当一个数字没有明确定义类型时,它此时是无符号的整数字面量,是可以和其他整型进行运算的。

package main

import "fmt"

func main() {
	f := 3.1415 // 默认类型:float64
	fmt.Println(f + 100) // 这个100就是无符号的整数字面量,编译器会根据另一个变量的类型,自动对它做隐式转换。
	// 也就是f是浮点,100也会被转换为浮点,从而完成计算。
}
=================调试结果=================
103.1415

8. 转义字符

转义字符是一种特殊的字符序列,以反斜杠()开头,后跟一个字符,用来表示一些特殊的含义。在Golang中,常见的转义字符包括:`

(换行符)、 (制表符)、'(单引号)、"`(双引号)等。通过使用转义字符,我们可以在字符串中表示一些特殊的字符和控制字符。

具体常见类型如下:

转义字符

Unicode字符的十六进制数字序列

作用

\a

U+0007

当解释器遇到转义字符 \a 时,它会发出哔哔声。

\b


  U+0008

表示退格(BS)字符。当解释器遇到转义字符\b时,它会将光标向左移动一格,覆盖前一个字符。

\f

U+000C

表示换页(FF)字符。当解释器遇到转义字符\f时,它会将光标移动到当前页面的顶部,并清除页面上的所有文本。

\n

U+000A

换行符,将光标移动到下一行的开头,从而开始新的一行。

\r

U+000D

回车符,将光标移动到当前行的行首,单独使用看不到效果。

\t

U+0009

横向制表符,相当于2个tab,也就是8个空格。

\v

U+000B

垂直制表符

\\

 U+005C

\会转义紧随其后的字符

\'

U+0027

单引号,当解释器遇到转义字符\'时,它会插入一个单引号字符到字符串中。

\"

  U+0022

双引号,作用同上。


9. 字符串

使用双引号或反引号引起来的任意个字符。它是字面常量。

9.1 定义字符串的方式

package main

import "fmt"

func main() {
	a := ""   // 定义一个空串
	a = "abc" // 这里表面看是abc覆盖了"",实际上:a最开始在内存中指向""的地址,后面重新指向了"abc",所以""和"abc"都是独立的个体,不违背常量不可修改的特性。最终""会被丢弃。
	fmt.Println(a)
}
=================调试结果=================
abc

9.2 结合转义字符使用

9.2.1 \"

package main

import "fmt"

func main() {
	a := ""
	// 这里由于引号的特殊性,必须用\把"转义成普通字符串才能成功打印,不然"""就会引起界定符冲突问题。
	a = "ab\"c"
	fmt.Println(a)

	a = "ab'c" // 双引号中可以包含单引号,不会引起界定符冲突的问题
	fmt.Println(a)

	a = `json: "name"` // 通过反引号的方式,也可以避免界定符冲突
	fmt.Println(a)
}

=================调试结果=================
ab"c
ab'c
json: "name"

9.2.2 \t

package main

import "fmt"

func main() {
	a := ""
	a = "ab\tc"
	fmt.Println(a)
}
=================调试结果=================
ab	c

9.2.3 \r\n

package main

import "fmt"

func main() {
	a := ""
	a = "ab\r\n\tc"
	fmt.Println(a)

	a = `ab
	c` // 该方式也可以表示上面的\r\n\t
	fmt.Println(a)
}
=================调试结果=================
ab
	c
ab
	c

9.3 字符串拼接

package main

import "fmt"

func main() {
	a := ""
	a = "123" + "xyz" // a="123"是一个常量,不能改变,内存中还有一个xyz,这俩组合起来,诞生了一个新的字符串"123xyz",并不是覆盖率原来的a
	fmt.Println(a)
}
=================调试结果=================
123xyz

9.4 字符串格式化

格式符参考fmt包帮助 https://pkg.go.dev/fmt

9.4.1 字符串

格式化符号

作用

%v

打印变量对应的值。不同的类型,产生不同的输出内容

%+v

对于结构体,会打印出字段名和值

%#v

对于结构体,有更加详细的输出

%T

打印值的类型

%%

打印百分号本身

9.4.2 整数

格式化符号

作用

%b

整型以二进制方式显示

%o

整型以八进制方式显示

%O

八进制带0o前缀

%x

十六进制小写

%X

十六进制大写

%U

把一个整数用Unicode格式打印。例如 fmt.Printf("%U, %x, %c\n", 27979, 27979,

27979) 输出 U+6D4B, 6d4b

%c

把rune、byte的整型值用字符形式打印

%q

把一个整型当做Unicode字符输出,类似%c,不过在字符外面多了单引号。q的意思就是quote。

9.4.3 浮点数

格式化符号

作用

%e、%E

科学计数法

%f、%F

小数表示法,最常用

%g

内部选择使用%e还是%f以简洁输出

%G

选择%E或%F


9.4.4 字符串或字节切片

格式化符号

作用

%s

字符串输出。如果是rune切片,需要string强制类型转换

%q

类似%s,外部加上双引号。q的意思就是quote

9.4.5 指针

格式化符号

作用

%p

指针,十六进制方式显示


9.4.6 总结

Go 1.19.4 语法基础_GO_07

9.5 特殊格式写法

package main

import "fmt"

func main() {
	a, b, c, d := 100, 200, 300, 400
    // %[2]v,这个2可以省略的,因为索引号是递增的
	fmt.Printf("%d, %[2]v, %[1]d, %d", a, b, c, d)
}
=================调试结果=================
100, 200, 100, 200

Go 1.19.4 语法基础_GO_08

9.6 Print函数

9.6.1 输出到标准输出

  • Print:接受任意数量的参数,使用空格分割,并将它们转换为字符串后连接在一起,然后输出到控制台。
  • Println:同上,最后追加换行
  • Printf:按照指定的格式符输出

9.6.2 输出到字符串

经常用来拼接字符串用

  • Sprint:相当于Print,不过输出为string
  • Sprintln:相当于Println,不过输出为string
  • Sprintf:相当于Printf,不过输出为string
package main

import "fmt"

func main() {
	a := '测'
	// fmt.Sprintf("%c", a)的作用并不是直接输出到控制台,而是输出为一个字符串,要想使用,必须用一个变量接住它
	b := fmt.Sprintf("%c", a)
	fmt.Println(b) // 输出变量b的值到控制台

	b = fmt.Sprintf("%c%s", a, "xyz") // 也可以这样实现字符串拼接
	fmt.Println(b)
}
=================调试结果=================
测
测xyz

10. 操作符

参考:https://golang.google.cn/ref/spec#Operators_and_punctuation

10.1 逻辑运算真值表

逻辑运算中,只有真和假,也就是bool中的true和false。

1为真,0为假。

10.1.1 与逻辑

可以理解为乘法或者理解为双方都为真结果才为真

A

B

F

0

0

0(假)

0

1

0(假)

1

0

0(假)

1

1

1(真)

10.1.2 或逻辑

可以理解为加法或理解为任意一方为真既最终结果为真

A

B

F

0

0

0(假)

0

1

1(真)

1

0

1(真)

1

1


1(真)

10.1.3 非逻辑

这个就是取反

A

F

0

1(真)

1

0(假)

10.2 算数运算符

主要就是+、-、*、/、%、++、--。

10.2.1 单目运算符(+、-、++、--)

上面说到了运算符种类,其中+、- 还可以当做正负用,不过就不是算数运算符了,而是单目运算符。

package main

import "fmt"

func main() {
	// 这里的-,代表的是负号,不是减号,并且-只作用在5上,这种就是单目运算符
	fmt.Println(5/2, -5/2)
}
=================调试结果=================
2 -2 // 整数除法丢弃余数,只返回整数
package main

import "fmt"

func main() {
	a := 5
    // 注意:a++只是一个语句,并不是表达式,所以不能像这里这样定义,会报错
	//fmt.Println(5/2, -a/2, a++)
	a ++ // 正确的定义方式(不可以++a)。a ++=a+=1=a=a+1
	fmt.Println(a)
}
=================调试结果=================
6

10.2.2 双目运算符

双目运算符其实指的是运算符作用在双方,比如5/2,/需要的是除数和被除数,这种就是双目运算符。

package main

import "fmt"

func main() {
	a := 5
	fmt.Println(5/2, -a/2)
}
=================调试结果=================
2 -2


10.2.3 常量计算问题

常量分为有类型常量和untyped无类型常量。

不同类型的常量是不能相互运算的(除非强制类型转换),但是无类型常量之间可以相互运算。

package main

import "fmt"

func main() {
	a := 5
	b := 3.5
	fmt.Println(a * b) // 这种是不可以的,因为5是int,b是float64
    
    // 这样可以,因为1和2.3都属于无符号字面常量
    // 其实也就是语法糖,编译器自动做了隐式类型转换,但是隐式类型转换往往是往精度更大的转,所以这里会转为浮点。
    // 1为无符号整型常量,2.3为无符号浮点型常量
    fmt.Println(1 * 2.3) // 官方称为无类型常量
    
    // 或者这样也行
    c := 1 * 2.5 // 官方称为无类型常量
    fmt.Println(c)
    fmt.Printf("%T %v\n", c, c)
}
=================调试结果=================
2.3
3.5
float64 3.5

10.3 位运算符

主要是这几个:

&位与、|位或、^异或(二进制同为0,异为1)、&^位清空、<<、>>

10.3.1 &位与(按位与运算)

应用场景:如判断奇偶数。

package main

import "fmt"

func main() {
	a := 0b10          // 0b10二进制对应十进制2
	fmt.Println(a & 1) // 2 与 1,与就是按位做乘法,实际的运算过程如下:
	// 2 0b 1 0
	//      * *
	// 1 0b 0 1
	// ----------
	//      0 0
	// 所以,a & 1 = 0
}
=================调试结果=================
0

10.3.2 |位或(按位或运算)

按位或运算,如果两个位都为 0,则结果为 0;否则,结果为 1。

package main

import "fmt"

func main() {
	a := 0b10          // 0b10二进制对应十进制2
	fmt.Println(a | 1) // 2 或 1,或就是按位做加法,但也并不完全是加法,0+0=0,其他都为1,实际的运算过程如下:
	// 2 0b 1 0
	//      + +
	// 1 0b 0 1
	// ----------
	//      1 1
	// 二进制0b11转十进制为3
	// 所以,a | 1 = 3
}
=================调试结果=================
3

10.3.3 >>(右移位)

package main

import "fmt"

func main() {
	fmt.Println(2 >> 2) // 把2向右移动2位。过程如下:
	// 2对应的二进制:0010,向右移动1位:0001,向右移动2位:0000 1,这个1已经没有位置了,所以为0
}
=================调试结果=================
0

10.3.4 &^位清空

&^位清空可以理解为&位与得出结果的取反值。

重点记住这句话:&^位清空的运算方式以第二个数为标准: 第二个数字的位值为1,则结果的对应位清零;如果第二个数字的位值为0,则结果的对应位采用第一个数的位值。

package main

import "fmt"

func main() {
	a := 0b1001
	fmt.Println(a & 3)
	fmt.Println(a &^ 3)
	// &^位清空逻辑如下
	// 首先是a & 3
	// 1 0 0 1
	// * * * *
	// 0 0 1 1
	// 0 0 0 1 = 1
	// 然后到a &^ 3,运算方式以第二个数为标准: 第二个数字的位值为1,则结果的对应位清零;如果第二个数字的位值为0,则结果的对应位采用第一个数的位值。
	// 1 0 0 1
	// 0 0 1 1
	// 1 0 0 0 = 8
}
=================调试结果=================
1
8
package main

import "fmt"

func main() {
	fmt.Println(15 & 5)
	// 15 1111
	// 5  0101
	// 5  0101
	fmt.Println(15 &^ 5)
	// 15 1111
	// 5  0101
	// 10 1010
}
=================调试结果=================
5
10

10.4 比较运算符

比较运算符组成的表达式,返回bool类型值。

成立返回True,不成立返回False。

主要是以下类型:

==、!=、>、<、>=、<=

但是注意:不同数据类型不能进行比较。

package main

import "fmt"

func main() {
	fmt.Printf("%T, %[1]v", "ABC" == "abc")
}
=================调试结果=================
bool, false

10.5 逻辑运算符

由于Go语言对类型的要求,逻辑运算符操作的只能是bool类型数据那么结果也只会是bool型。

主要是一下几个:

&&、||、!


逻辑运算符优先级

(1)!:最高

(2)&&:第二

(3)||:最低

10.5.1 &&

与运算中,有短路这个概念,就是第一个条件为假的话,就触发短路,结束了。

所以实际运用中,可以把短路条件配置在最前面,失败就停止运行,减少运算量。

package main

import "fmt"

func main() {
	fmt.Println(123 > 234 && "abc" > "ABC")
	// 逻辑真值表中,只有真真为真,其他都为假。
	// 所以123 > 234为假,触发了逻辑运算中的短路,就直接结束了,不会再执行"abc" > "ABC"了。
}
=================调试结果=================
false

10.5.2 ||

package main

import "fmt"

func main() {
	// 逻辑真值表中,或逻辑中只要有一方为真,结果就为真。
	fmt.Println(123 > 234 || "abc" > "ABC") // 字符串的比较,实际上对比的是ASCII码值
	// a=97,b=98,c=99
	// A=65,B=66,C=67
}
=================调试结果=================
true
10.5.2.1 使用||时的注意事项
package main

import "fmt"

func main() {
	fmt.Println(1 || "abc" > "ABC") // 注意这样是不可以的
    // 在逻辑运算中,GO要求操作数必须是bool类型,否则不能逻辑运算,并且不提供隐式转换。
}

10.6 赋值运算符

符号

含义

示例

=

将等号右边的值,赋值给等号左边的标识符

var a = 1

:=

短格式赋值

a := 1

+=

将等号右边的值相加,得出的结果赋值给等号左边的标识符

var a += 1

var a = a + 1

-=

将等号右边的值相减,得出的结果赋值给等号左边的标识符

var a -= 1

var a = a - 1

*=

将等号右边的值相乘,得出的结果赋值给等号左边的标识符

var a *= 1

var a = a * 1

/=

将等号右边的值相除,得出的结果赋值给等号左边的标识符

var a /= 1

var a = a / 1

%=

将等号右边的值取余,得出的结果赋值给等号左边的标识符

var a %= 1

var a = a % 1

>>=

右位移后赋值

var a >>= 1

var a = a >> 1

<<=

左位移后赋值

var a <<= 1

var a = a << 1

&=

按位与后赋值

var a &= 1

var a = a & 1

&^=

位清空后赋值

var a &^= 1

var a = a &^ 1

^=

位异或后赋值(二进制同为0,异为1)

var a ^= 1

var a = a ^ 1

|=

位或后赋值

var a |= 1

var a = a | 1


10.7 三元运算符

Go 中没有三元运算符。

没有 ?: 的原因是,语言的设计者看到这个操作经常被用来创建难以理解的复杂表达式。

在替代方案上,if-else 形式虽然较长,但无疑是更清晰的。

一门语言只需要一个条件控制流结构。


10.8 指针操作

数据是放在内存中,内存是线性编址的。任何数据在内存中都可以通过一个地址来找到它。

&:取出内存地址

*:解析内存地址(就是把地址中的内容拿出来),表示通过指针取值

10.8.1 & 取出内存地址

package main

import "fmt"

func main() {
	a := 100
	b := &a // 注意:这里的&是一个单目运算符,不是位与双目运算符。主要就是为了取出a的内存地址。
	fmt.Println(b)
}
=================调试结果=================
0xc000120058 // 这就是100对应的内存地址。

10.8.2 * 解析内存地址

package main

import "fmt"

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

	c := *b
	fmt.Println(c)
}
=================调试结果=================
100

10.8.3 问题思考:上述赋值后,所有变量的内存地址是否相同

package main

import "fmt"

func main() {
	a := 100 // a指向100,并且100的内存地址为0xc0000b6058
	b := &a // b指向100的内存地址0xc0000b6058,相当于b = 0xc0000b6058
	fmt.Println(b)
    // 但是,*b,是把内促地址下的值拿出来了,就相当于c = 100
    // c = 100,会在系统中重新开辟一块内促来存储这个100,让c指向这个新的内存地址
    // 所以b == &c不成立
	c := *b 
	fmt.Println(a == c, b == &c)
    
    var d = a // d 指向100
    // a和b都指向100,所以a == d
    // b指向0xc0000b6058,但是d也是在一个新的内存地址中来存储100的,所以b == &d不成立
	fmt.Println(a == d, b == &d)
}
=================调试结果=================
0xc0000b6058
true false
true false

10.9 运算符优先级

表中优先级由高到低。

单目 > 双目。

算数 > 移位 > 比较 > 逻辑 > 赋值。

搞不清,用括号,避免产生歧义

Go 1.19.4 语法基础_GO_09