Go 1.19.4 语法基础-Day 02

1. 注释

1.1 多行注释

1.1.1 方式一(不推荐使用)

package main

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

}

在这里插入图片描述

1.1.2 方式二(推荐)

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

package main

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

}

在这里插入图片描述

1.2 单行注释

package main

import "fmt"

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

1.3 TODO 提示功能没有完成

1.3.1 安装插件 todo tree

在这里插入图片描述

1.3.2 最终效果
package main

import "fmt"

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

在这里插入图片描述

类似的还有:
// 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语言的多重赋值是同时执行的

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 整型

(1)长度不同有符号
int8(1个字节)、int16(C语言short,2个字节)、int32(4个字节)、int64(C语言long,8个字节)
(2)长度不同无符号
uint8(1个字节)、uint16(2个字节)、uint32(4个字节)、uint64 (个字节)。byte类型,它是uint8的别名
(3)自动匹配平台: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转换成十进制方式就是如下图:
在这里插入图片描述

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​​81字节-128 到 127
int16​​162字节-32,768 到 32,767
int32​​(别名rune)324字节-2,147,483,648 到 2,147,483,647
int64​​648字节-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)​​81字节0 到 255
uint16​​162字节0 到 65,535
uint32​​324字节到 4,294,967,295
uint64​​648字节0 到 18,446,744,073,709,551,615

7.1.3 Go中的进制前缀表示法

前缀对于编译器解析数字文字非常重要。
如果没有前缀,编译器会将数字解释为十进制数。

进制类型前缀
二进制0b(b可以大写可小写)
八进制0或0o(可以省略,但是不要,避免歧义,o可以大写也可以小写)
十进制默认无前缀
十六进制0x(X可以大写也可以小写)
十六进制字符‘\x’

例如:
二进制数:​​0b1010​​
八进制数:​​010​​​、​​0o10​​
十进制数:​​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 // 浮点数

在这里插入图片描述

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 27979int32 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字符的十六进制数字序列作用
\aU+0007当解释器遇到转义字符 \a 时,它会发出哔哔声。
\bU+0008表示退格(BS)字符。当解释器遇到转义字符\b时,它会将光标向左移动一格,覆盖前一个字符。
\fU+000C表示换页(FF)字符。当解释器遇到转义字符\f时,它会将光标移动到当前页面的顶部,并清除页面上的所有文本。
\nU+000A换行符,将光标移动到下一行的开头,从而开始新的一行。
\rU+000D回车符,将光标移动到当前行的行首,单独使用看不到效果。
\tU+0009横向制表符,相当于2个tab,也就是8个空格。
\vU+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 总结

在这里插入图片描述

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

在这里插入图片描述

9.6 Print函数

9.6.1 输出到标准输出

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

9.6.2 输出到字符串

经常用来拼接字符串用
(1)Sprint:相当于Print,不过输出为string
(2)Sprintln:相当于Println,不过输出为string
(3)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 与逻辑

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

ABF
000(假)
010(假)
100(假)
111(真)
10.1.2 或逻辑

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

ABF
000(假)
010(真)
100(真)
111(真)

10.1.3 非逻辑

这个就是取反

AF
01(真)
10(假)

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 运算符优先级

表中优先级由高到低。
单目 > 双目。
算数 > 移位 > 比较 > 逻辑 > 赋值。
搞不清,用括号,避免产生歧义
在这里插入图片描述

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值