Golang入门

本文介绍了Golang的基础知识,包括其与C语言的关系、包的概念、垃圾回收机制、并发特性、goroutine和channel的使用。同时,详细讲解了Go语言的执行流程、开发注意事项、数据类型、命名规范和运算符等,适合初学者入门。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

  1. GO 从C语言中继承了很多 理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和C语言一样的编译执行方式及弱化的指针。

  2. 引入 的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在。

  3. 垃圾回收机制,内存自动回收,不需开发人员管理

  4. 天然并发
    (1)从语言层面支持并发,实现简单。
    (2) goroutine,轻量级线程,可实现大并发处理,高效利用多核。
    (3)基于CPS并发模型(Communicating Sequential Processes )实现。

  5. 吸收了管道通信机制,形成 Go 语言特有的管道 channel ,通过管道,可以实现不同的 goroute 之间的相互通信。

  6. 函数可以返回多个值。

Go 的 hello world

下面书写一个 go 版本的 hello world

package main
import "fmt"
func main(){
	fmt.Println("hello ,world")
}
  1. 编译

go build hello.go

参数说明:

-o 指定编译后文件的名称

# 通过 build 命令将文件转化为二进制
# 指向完下面的命令后,会生成一个 hello 的一个文件
$ go build hello.go
  1. 运行
$ ls
hello    hello.go
$ hello
hello, world

说明

go 为了方便测试,也可以通过 go run 直接进行运行,类似于执行一个脚本文件,但是不能用于生产环境。

这个过程中也是会编译成二进制源码的,因此速度相对来说较慢。

如果此时 demo 里面有错误,编译的时候会进行提示,例如如下代码:

package main
// 引入  fmt 就表示可以使用 fmt 包的函数
import "fmt"

func main(){    // 表示主函数(程序的入口)
	fmt.Println("hello, world")
	fmt.Pri232("hello, world")
}

编译:

$ go build hello.go
# command-line-arguments
./hello.go:7:2: undefined: fmt.Pri232

Golang 的执行流程分析

  • 如果是对源码编译后,再执行,Go 的执行流程如下图:
XOadn1
- 如果是对源码进行编译后,再执行,Go 执行流程如下:
VqbfKB
- 两种方式的区别:
  1. 如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器
    上,仍然可以运行

  2. 如果我们是直接go run执行 go 源代码,那么如果要在另外-一个机器上这么运行,也需要go开发环境,
    否则无法执行。

  3. 在编译时,编译器会将程序运行依赖的库文件包含在可执行文件中,所以,可执行文件变大了很多。

开发注意事项

杂项

  1. Go源文件以"go"为扩展名。
  2. Go应用程序的执行入口是main()函数。
  3. Go语言严格区分大小写。
  4. Go方法由一条条语句构成,每个语句后不需要分号(Go语言会在每行后自动加分号),这也体现出Golang的简洁性。
  5. Go编译器是一行行进行编译的,因此我们一行就写一条语句,不能把多条语句写在同一个,否则报错
  6. go语言定义的变量或者 import 的包如果没有使用到,代码不能编译通过。
1X71WO
  1. 大括号都是成对出现的,缺一不可。
  2. Go 当中,一个包只能有一个 main 函数,如果有同名的话,会报错。

解决方案:新建一个文件夹

FE71Rj
## 配置问题
  1. vscode 安装 go 插件失败。

解决办法:

# 打开 GO111MODULE, 并切换为国内源
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct

重新打开 vscode,选择一键安装。

更多可以参考此篇文章:

Golang 开发使用 VSCode 完全配置指南

老外的 Go Module 介绍

官方的 vs-code

  1. 如果我们希望自己的 GO 项目在 GOPATH/src外面进行运行的话,需要基于 Go Module 来进行创建:
  • 例如我们想要在~/Coding/tour 这个目录下创建一个工程
~/Coding/go ⌚ 15:00:55
$ mkdir tour  
~/Coding/go ⌚ 15:01:02
$ cd tour 
~/Coding/go/tour ⌚ 15:01:10
$ go mod init flysnow.org/tour
go: creating new go.mod: module flysnow.org/tour

~/Coding/go/tour ⌚ 15:01:20
$ ls
go.mod

~/Coding/go/tour ⌚ 15:01:22
$ cat go.mod 
module flysnow.org/tour

go 1.16

这个时候我们打开 VSCODE,写入如下内容:

fXdBaK
此时我们就已经可以在 main.go 当中引用 Hero 这个变量了。
package main

import (
	"fmt"
	"flysnow.org/tour/model"
)

func main() {
	fmt.Println("Hello World")
	fmt.Printf("model.Hero: %v\n", model.Hero)
}

基础内容

1. Go 转义字符

字符说明
\t一个制表位,实现对其功能
\n换行符
\\一个 \
\"一个“
\r一个回车,fmt.Println(“天龙八部雪山飞狐/r张飞”)

2. 注释

行注释

// 大家好,我是注释内容

块注释

/*
	大家好,我是块注释
*/

3.代码格式规范

格式规范

需要正确的进行代码的缩进和对其,可以通过命令行的方式来进行格式的调整:

gofmt -w main.go
  • -w 参数:该参数用来将格式化完成的代码写回到 main.go 这个文件中。如果没有 -w参数的话,只能打印到控制台上。

代码风格

函数的括号只能以一种方式来进行,否则编译器报错:

// 正确的形式
func main() {
	// 演示转义字符的使用
	fmt.Println("tom\rjack")
	fmt.Println("C:\\Users\\Administrator\\Desktop")
	fmt.Println("tom 说:\"i love you\"")
}

Go 语言不允许使用如下的方式来书写:

8fwVHt

4. 文档内容

  • 官方的接口文档位于:接口文档

  • 官方的简易教程文档:A Tour of Go

  • Mac 系统中查看 Go 的源码,位于:

/opt/homebrew/Cellar/go/1.16.5/libexec/src    # 里面是一堆包
# 变量的使用

1. 单个变量的声明方式

  • 声明变量并指定类型
func main() {
	// 定义变量, 声明变量后, 若不赋值, 使用默认值,int 的默认值为 0
	var i int 
	// 给 i 赋值
	i = 10
	fmt.Println("i=", i)
}

打印输出:

i= 10
  • 根据值自行判定变量类型
func main() {
	var num  = 10.11
	fmt.Println("num = ", num);
}

打印输出:

num =  10.11
  • 省略 var 进行声明。如果声明时就直接赋值,可以省略数据类型。

注意: := 左侧的变量至少一个是已经声明过的,否则会导致编译错误。

func main() {
	name := "tom"
	// 下面的方式等价于 var name string; name ="tom"
	fmt.Println("name=",name)
}

2. 一次性声明多个变量

  • 通过 var 一次性声明多个变量,并使用默认值
func main() {
	var n1, n2, n3 int
	fmt.Println("n1=",n1,"n2=",n2,"n3=",n3)
}

打印输出:

n1= 0 n2= 0 n3= 0
  • 根据值进行类型推到
func main() {
	n1, name, n3 := 100, "tom~", 888
	fmt.Println("n1=", n1, "name=", name, "n3=", n3)
}

打印输出:

n1= 100 n2 0 name= tom~
  • 通过括号进行多个声明:
var (
	n3 = 300
	n4 = 900
	name2 = "marry"
)
func main() {
    // 输出全局变量
	fmt.Println("n1=", n1, "n2", n2, "name=", name)
}

打印输出:

n3= 888 n4 0 name2= marry

总结:因为简洁和灵活的特点,简短变量声明被广泛用于大部分的局部变量的声明和初始化。var 形式的声明语句往往是用于需要显式指定变量类型地方,或者因为变量稍后会被重新赋值而初始值无关紧要的地方。

3. 注意事项

  • 变量可以在作用域内进行改变,但是不能改变其变量类型
btUerI
- 变量在 **同一作用域**内不能重名
func main() {
	var i int = 10
	i = 30
	i = 50
	// i = 1.2 // 不能改变数据类型
	fmt.Println("i=", i)


	// 变量在同一个作用域内(在一个函数或者在代码块里)内不能重名
	var i int = 59
}
KS3vKg
- Golang 的变量如果没有赋值,编译器会使用默认值,比如 int 默认值为0, str 的默认值是空串。

4. 变量中 + 的使用

在 Go 当中,+ 和 Python 中的用法类似:

  • 如果两边都是数值类型的话,做加法运算。
  • 当左右两边都是字符串的话,则做字符串拼接。
package main

import "fmt"

func main() {
	var i = 1
	var j = 2
	var r = i + j
	fmt.Println("r=", r)
	var str1 = "hello"
	var str2 = "world"
	var res = str1 + str2
	fmt.Println("res=", res)

}

GO 语言的数据类型

Wt1CFZ
## 1. 整形数据类型

一位表示 8 个字节

类型有无符号占用存储空间表示范围
int81字节-128~127
int162字节 − 2 15 -2^{15} 215 ~ 2 15 − 1 2^{15}-1 2151
Int324字节 − 2 31 -2^{31} 231 ~   2 31 − 1 ~2^{31} - 1  2311
int648字节 − 2 63 -2^{63} 263 ~ 2 63 − 1 2^{63} - 1 2631
  • 测试 int8 的范围
package main
import "fmt"

func main()  {
	var i int = 1
	fmt.Println("i=", i)
	
	// 测试一下 int8 的范围 -128~ 127
	var j int8 = 128
	fmt.Println("j=", j)
}

打印输出:
# 可以看到上面已经开始出现报错的红色下划线信息了
$ go run main.go
# command-line-arguments
./main.go:9:6: constant 128 overflows int8
  • 测试 uint8(0-255) 的范围
package main
import "fmt"
func main()  {
	// 测试一下 uint8 的范围(0-255),其它的 uint16,uint32,uint64 一样
	var k int = -1
	fmt.Println("k=", k)
}

打印输出:

$ go run main.go
# command-line-arguments
./main.go:13:6: constant -1 overflows uint8
类型有无符号占用存储空间表示范围备注
int32位系统 4 个字节
32位系统 8 个字节
− 2 31 -2^{31} 231 ~   2 31 − 1 ~2^{31} - 1  2311
− 2 63 -2^{63} 263 ~   2 63 − 1 ~2^{63} - 1  2631
uint32位系统 4 个字节
32位系统 8 个字节
0 ~   2 32 − 1 ~2^{32} - 1  2321
0 ~ 2 64 − 1 2^{64} - 1 2641
rune与 int32 一样 − 2 31 -2^{31} 231 ~ 2 31 − 1 2^{31} - 1 2311等价 int32,
表示一个 unicode编码
byte与 int8 一样0~255当要存储字符时,
选用 byte
package main

import "fmt"

func main() {
	// int, uint, rune, byte 的使用
	var a int = 8900
	fmt.Println("a=", a)
	var b uint = 0 // -1 的话会抛出越界异常, uint 的范围是 0 - 2^{32} - 1
	var c uint = 255
	fmt.Println("b = ", b, "c = ", c)

	var d rune = 0
	fmt.Println("d=", d)
}
IhzK00
打印输出:
a = 8900
b =  0 c =  255
d= 0

整型的使用细节:

  1. Golang 各整数类型分:有符号无符号 , int uint 的大小和系统有关。
  2. Golang 的整型默认声明为 int 型
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 整型的使用细节
	var n1 = 100 // n1 是什么类型
	// fmt.Printf() 可以用于格式化输出
	fmt.Printf("n1 的类型是 %T\n", n1)

}

打印输出:

n1 的类型是 int
  1. 如何在程序中查看某个变量的字节大小和数据类型
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	var i int = 1
	fmt.Println("i=", i)
	// 如何在程序中查看某个变量的字节大小和数据类型(使用较多)
	var n2 int64 = 10
	fmt.Printf("n2 的类型是 %T n2 占用的字节数是 %d", n2, unsafe.Sizeof(n1))
}

打印输出:

n2 的类型是 int64 n2 占用的字节数是 8
  1. Golang 程序中整型在使用时,遵守保小不保大的原则,即:在保证程序正确运行情况下,尽量使用空间小的数据类型。【如:年龄】
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	// 保小不保大的原则
	var age byte = 90
	fmt.Println("age = ", age)
}

打印输出:

age =  90
  1. bit:计算机中的最小存储单位。byte 是计算机中的基本存储单元:1 byte= 8 bit

2.浮点型

浮点表示小数,简单使用的话,和 Python 没有太大的差别,默认的浮点类型是 float64

package main

import "fmt"

// 演示小数类型的使用
func main() {
	var price float32 = 89.12
	var n1 = 3.2
	fmt.Printf("n1 的类型是: %T\n", n1)
	fmt.Println("price = ", price)
}

打印输出:

$ go run main.go
n1 的类型是: float64
price =  89.12

小数类型分类

类型占用存储空间表示范围
单精度 float324 字节-3.403E38 ~ 3.4003E38
双精度float648字节-1.798E308 ~ 1.798E308
  • 浮点树在机器的存放形式的简单说明:浮点数 = 符号位 + 指数位 + 尾数位
  • 说明:浮点数都是有符号的
package main

import "fmt"

// 演示小数类型的使用
func main() {
	var num1 float32 = 0.000089
	var num2 float64 = -7809656.09
	fmt.Println("num1 = ", num1, "num2= ", num2)
}

打印输出:

$ go run main.go
n1 的类型是: float64
price =  89.12
num1 =  8.9e-05 num2=  -7.80965609e+06
  • 尾数可能丢失,造成精读损失。 -123.0000901
package main

import "fmt"

// 演示小数类型的使用
func main() {

	// 尾数可能丢失, 造成精读损失 -123.0000901
	var num3 float32 = -123.0000901
	var num4 float64 = -123.0000901
	fmt.Println("num3 = ", num3, "num4=", num4)
}

说明:float64 的精读比 float32 的要准确。 如果精度要求比较高, 使用 float64

  • Go 和 Python 一样,也同样可以通过科学计数法的形式来定义浮点类型。
package main

import "fmt"

// 演示小数类型的使用
func main() {
// 十进制形式
	num6 := 5.12
	num7 := .123
	fmt.Println("num6:", num6, "num7=", num7)

	// 科学计数法形式
	num8 := 5.1234e2
	num9 := 5.1234e2
	num10 := 5.1234e-2
	fmt.Println("num8=", num8, "num9=", num9, "num10=", num10)

}

打印输出:

num6: 5.12 num7= 0.123
num8= 512.34 num9= 512.34 num10= 0.051234

总结:

  • Golang 的浮点型默认声明方式为 float64 类型。

  • 开发当中推荐使用 float64

3.字符类型

Go 语言的字符型通过 byte 来定义,对应 Python 语言的 bytes。

Go 当中的 byte 是 uint8 的别名,其中一个区别是 Go 保存汉字的话,默认采用的是 ASCII 表,会发生溢出问题。

Python 对应的 bytes 使用示例

In [64]: c1 = bytes("a",encoding="ascii")

In [65]: ord(c1)
Out[65]: 97

In [66]: c2 = bytes("0",encoding="ascii")

In [67]: ord(c2)
Out[67]: 48

In [68]: c1.decode()
Out[68]: 'a'

In [69]: c2.decode()
Out[69]: '0'

In [74]: ord('北')
Out[74]: 21271

GO 当中 byte 的使用示例

package main

import "fmt"

func main() {
	var c1 byte = 'a'
	var c2 byte = '0'

	// 当我们直接输出 byte 值时, 输出的是它对应的 ASCII 码值
	fmt.Println("c1 = ", c1, "c2 = ", c2)

	// 如果我们希望输出对应的字符,使用格式化输出
	fmt.Printf("c1=%c,c2=%c\n", c1, c2)
	// var c3 byte = "北"  北对应的编码超过了 ASCII 码表的值, 会溢出
	var c3 int = '北'
	fmt.Printf("c3=%c, c3对应的码值为:%d, 类型是 %T", c3, c3, c3)
}

打印输出:

$ go run main.go
c1 =  97 c2 =  48
c1=a,c2=0
c3=北, c3对应的码值为:21271, 类型是 int

代码说明:

  1. 如果我们保存的字符在 ASCII 码表的,比如[0-1, a-z, A-Z…] 直接可以保存到 byte。
  2. 如果我们保存的字符对应码值大于 255,这时我们可以考虑使用 int 类型保存。
  3. 如果我们需要安装字符的方式输出,这时我们需要格式化输出,即 fmt.Printf(%c,c1)

总结:

在 Go 当中,字符的本质是一个整数,直接输出是一个 UTF-8 的码值。因此我们可以给某个变量赋一个数字,然后按格式化输出 %c,会输出对应的 unicode 字符。

  • 如果针对一个中文进行字符的转化,因为 ASCII 码表的数量不够,需要采用 int 类型来进行赋值:

    package main
    
    import "fmt"
    
    func main() {
    	var c3 int = '北'
        fmt.Printf("c3=%c, c3对应的码值为:%d, 类型是 %T\n", c3, c3, c3)
    }
    
  • GO 里面允许使用转义字符 \n 来将其后的字符转变为特殊字符型常量。例如:

    package main
    
    import "fmt"
    
    func main() {
    	var c4 = '\n'
    	fmt.Printf("c4= %d", c4)
    }
    
  • 字符类型是可以进行运算的,相当于一个整数,因为它都有对应的 Unicode 码。

    package main
    
    import "fmt"
    
    func main() {
    	// 字符类型是可以运算的,相当于一个整数,运算时是按照码值运行
    	var n1 = 10 + 'a'
    	fmt.Println("n1=",n1)
    }
    

    打印输出:

    n1= 107
    
  • GO 语言的编码都统一成了 utf-8,几乎不会出现编码乱码的困扰了。

对于 Python 来说,可以选择不同的编码方式,其本质是一个 bytes 对象。

4.布尔类型

GO 里面的布尔类型只能允许取值 TRUE 或者 FALSE。

bool 类型占一个字节。

JHM3Ip
## 5.字符串类型

字符串就是一串固定长度的字符连接起来的字符序列。 Go的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。

基本使用

package main

import (
	"fmt"
)

func main()  {
	var address string = "长城我来了 hello world"
	fmt.Println(address)
}

打印输出:

$ go run main.go
长城我来了 hello world

不同的字符串定义形式

1)双引号,会识别转义字符

2)反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果

package main

import (
	"fmt"
)

func main()  {
	var address string = "长城我来了 hello world"
	fmt.Println(address)

	// 字符串一旦赋值了,就不能更改了。为不可变类型
	// var str = "hello"
	// str[0] = 'a' //  这里不能修改 str 的内容

	// 字符串的两种表示形式3(1)双引号,会识别转义字符3(2)反引号,
	// 以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、
	// 输出源代码等效果
	str2 := "abc\nabc"
	fmt.Println(str2)
	str3 := `
	package main

	import "fmt"
	
	// 演示小数类型的使用
	func main() {
		var price float32 = 89.12
		var n1 = 3.2
		fmt.Printf("n1 的类型是: %T\n", n1)
		fmt.Println("price = ", price)
	
	}
	`
	fmt.Printf("%T %s",str3,str3)
}

字符串的拼接

go 的字符串拼接和 Python 类似,通过 + 来进行拼接。

package main

import (
	"fmt"
)

func main() {
	// 字符串的拼接方式
	var str = "hello" + "world"
	str += "haha!"

	fmt.Println("str:", str)
	// 字符串的换行
	str4 := "hello" + "world" + "hello" + "world" + "hello" +
		"world" + "hello" + "world"
	fmt.Println("str4:", str4)
}

打印输出:

str: helloworldhaha!
str4: helloworldhelloworldhelloworldhelloworld

区别的地方就是,如果需要拼接的字符串长度过长的话,GO 要求将 + 放在换行符之前,而 Python 则是需要在换行的地方加上 \

6. 指针

指针就是针对某个内存空间的一个引用。

  • 获取变量的地址,用 &, 比如 var num int, 获取 num 的地址: &num

内存图:

NUfcAJ
  • 对于指针变量来说,其存放的是一个地址,对应里面存放地址的内存空间才是真正的值。
  • 获取指针指向的值,使用 *ptr
package main

import "fmt"

// 指针类型
func main() {
	var i int = 10
	fmt.Printf("i的地址=%p\n", &i)
    
	// 1. ptr 是一个指针变量
	// 2. ptr 的类型是 *int
	// 3. ptr 本身的值是 &i
	var ptr *int = &i
	fmt.Printf("ptr=%v\n", ptr)
	fmt.Printf("ptr 的地址是=%p", &ptr)
	fmt.Printf("ptr 指向的值是:%v", *ptr)
}
$ go run main.go
i的地址=0x140000b6008
ptr=0x140000b6008
ptr 的地址是=0x140000ba020ptr 指向的值是:10
5OziYU

这里我们可以将指针这个概念和门牌号进行类比:

i 指向了绿色这个门,对应的门牌号是 0x140000b6008,里面住的是 10 号同学。

这个时候 ptr 和大家开了一家房屋中介,它把 i 指向的大门地址写成在了一张纸上,放在了蓝色小屋里面,蓝色小屋的门牌号是 0x140000b6008

如果我们想要拿到 ptr 里面的地址纸条,那么fmt.Printf(“%v”,ptr) 就可以直接获取了。

如果希望通过 ptr 来找到绿色屋子里面的 10 号同学,那么我们需要通过 *fmt.Printf(“%v”, ptr) 来获取。也就是说,需要加一个 *

坑点:

package main

import "fmt"

func main() {
	var a int = 300
	var ptr1 *int = a   // 错误,这里接受的应该是一个指针变量 应该改成 &a
    var ptr2 *float32 = &a // 错误, 这里类型不匹配,a 的类型为  int,应该用 *int 来接收
	fmt.Printf("%v", ptr)
}

说明:

  • 每一种类型的指针都有其对应的类型,形式为 *数据类型,比如 int 对应的指针就是 *int,float32 对应的类型就是 *float,一次类推。
  • 类型包括:int,float,bool,string,数组和结构体【struct】。

基本数据类型的默认值

数据类型默认值
整型0
浮点型0
字符串“”
布尔类型false

在 GO 当中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值。在 GO 当中,默认值又被称为 0 值。

package main

import (
	"fmt"
)

func main() {
	var a  int
	var b  float32
	var c float64
	var isMarried bool
	var name string
	// 这里 %v 表示按变量的值来输出
	fmt.Printf("a = %d, b = %f, c= %f, isMarried = %v, name = %v",a,b,c,isMarried,name)
}

打印输出:

a = 0, b = 0.000000, c= 0.000000, isMarried = false, name = 

基本数据类型的相互转化

1 . 数值类型的转化

GO 的数据类型转化和 Python 类似,都是要进行显示声明的:

package main

import (
	"fmt"
)

func main() {

	var i int32 = 100
	// 希望将 i=> float
	var n1 float32 = float32(i)
	var n2 int8 = int8(i)
	var n3 int64 = int64(i)

	fmt.Printf("i = %v, n1= %v, n2= %v, n3 = %v", i, n1, n2, n3)
}

打印输出:

$ go run main.go
i = 100, n1= 100, n2= 100, n3 = 100

说明:

  • 被转化的变量是不会发生变化的,也就是对新的值另外开辟一个空间。
  • 在转化的过程中,如果将 int64转化为int64,编译时不会报错,而是按二进制位溢出处理,和我们想象的结果不太一样。

例如下面这段代码:

package main

func main() {
	// 测试
	var n1 int32 = 12
	var n2 int64
	var n3 int8
	n2 = n1 + 20  // int32 ---> int64 错误
	n3 = n1 + 20  // int32 ---> int8 错误
}

因此必须进行显式的转化:

package main

import "fmt"

func main() {
	// 测试
	var n1 int32 = 12
	var n2 int64
	var n3 int8
	n2 = int64(n1) + 20  // int32 ---> int64 错误
	n3 = int8(n1) + 20  // int32 ---> int8 错误
	fmt.Printf("n1= %d, n2=%d, n3= %d",n1,n2,n3)
}

关于 int 类型的坑,还有编译通过和不通过的问题:

package main

import "fmt"

func main() {
	var n1 int32 = 12
	var n3 int8 
	var n4 int8
	n4 = int8(n1) + 127 //  编译不通过,但是
	n3 = int8(n1) + 128  //  编译不通过
	fmt.Println(n3,n4)
}
ZkaVKk

从上面的代码来看,如果数值小于对应的数值类型,编译时不会出错,而是在结果计算的时候将值进行溢出处理。但是如果超过了对应的数值类型最大值,则会编译失败。

2.其他类型转字符串类型

  • GO 语言的占位符

普通占位符

占位符说明举例输出
%v相应值的默认格式。Printf("%v", people){zhangsan},
%+v打印结构体时,会添加字段名Printf("%+v", people){Name:zhangsan}
%#v相应值的Go语法表示Printf("#v", people)main.Human{Name:“zhangsan”}
%T相应值的类型的Go语法表示Printf("%T", people)main.Human
%%字面上的百分号,并非值的占位符Printf("%%")%

布尔占位符

占位符说明举例输出
%ttrue 或 falsePrintf("%t", true)true

整数占位符

浮点数和复数的组成部分(实部和虚部)

  • 使用 Sprintf 进行转化
package main

import (
	"fmt"
)

func main() {
	var num1 int = 99
	var num2 float64 = 23.456
	var b bool = true
	var myChar byte = 'h'
	var str string // 空的 string

	// 使用第一种方式进行转化
	str = fmt.Sprintf("%d", num1) // Sprintf 表示格式化并返回一个字符串,不做输出打印
	fmt.Printf("str type %T str=%q\n", str, str) //  %q占位符表示字符的面值,并用引号(quote)包裹

	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str type %T str=%q\n", str, str)

	str = fmt.Sprintf("%t", b)
	fmt.Printf("str type %T str=%q\n", str, str)

	str = fmt.Sprintf("%c", myChar)
	fmt.Printf("str type %T str= %q\n", str, str)
}

打印输出:

$ go run main.go
str type string str="99"
str type string str="23.456000"
str type string str="true"
str type string str= "h"
  • 使用 strconv 包的函数
    • func FormatInt(i int64, base int) string
    • func FormatFloat(f float64, fmt byte, prec, bitSize int) string
    • func FormatBool(b bool) string
    • func Itoa(i int) string
      • 等价于 FormatInt(int64(i), 10)
package main

import (
	"fmt"
	"strconv"
)

func main() {
	// 第二种方式  strconv 函数
	var num3 int = 99
	var num4 float64 = 23.456
	var b2 bool = true
    
	str = strconv.FormatInt(int64(num3), 10)
	fmt.Printf("str type %T str=%q\n", str, str)

	// f 表示接受的是 float, 10: 表示小数保留 10 位, 64 表示这个小数是 float64
	str = strconv.FormatFloat(num4, 'f', 10, 64)
	fmt.Printf("str type %T str=%q\n", str, str)

	str = strconv.FormatBool(b2)
	fmt.Printf("str type %T str =%q\n", str, str)
    
	// strcov 包中有一个 Itoa 函数, 等价于 FormatInt(int64(i), 10)
	var num5 = 4567
	str = strconv.Itoa(num5)
	fmt.Printf("str type %T, str is %q\n", str,str)
}

打印输出:

str type string str="99"
str type string str="23.4560000000"
str type string str ="true"
str type string, str is "4567"

使用建议fmt 这个包底层使用了反射的机制,效率较低,因此符号转化的时候建议使用strconv 这个包。

3.字符串转其它数据类型

这里我们使用的也是 strconv 包的函数:

  • func ParseBool(str string) (bool, error)
package main

import (
	"fmt"
	"strconv"
)
func main() {
	var str string = "true"
	var b bool
	// 和  Python一样,通过 _ 来忽略值
	b, _ = strconv.ParseBool(str)
	fmt.Printf("b type %T b =%v\n", b, b)
}

打印输出:

b type bool b =true
  • func ParseFloat(s string, bitSize int) (float64, error)
package main

import (
	"fmt"
	"strconv"
)

func main() {
	var str3 string = "123.456"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1 type is %T, f1 = %v\n", f1, f1)
}

打印输出:

f1 type is float64, f1 = 123.456
  • func ParseInt(s string, base int, bitSize int) (i int64, err error)

  • func ParseUint(s string, base int, bitSize int) (uint64, error)

base参数表示以什么进制的方式去解析给定的字符串,有效值为0、2 - 36。当 base=0 的时候,表示根据 string 的前缀来判断以什么进制去解析:0x开头的以16进制的方式去解析,0开头的以8进制方式去解析,其它的以 10 进制方式解析。

而当 base 为 2 - 36 中的某一个数时,例如 base = 5,那么就按 5 为进制来进行解析。

bitSize参数表示转换为什么位的 int/uint,有效值为0、8、16、32、64,分别表示转化为 int,int8,int16 和 int64 的数据类型。当bitSize=0的时候,表示转换为 int 或 uint 类型。例如 bitSize = 8 表示转换后的值的类型为int8 或 uint8。

经测试验证,bitSize 这里只能填入 64 这个数字,填入别的数字无法进行转化【因为返回的只能是 int64 】。因此如果我们希望获得其它类型的数字的话,需要进行进一步的强转:num = int32(num5)

示例:

package main

import (
	"fmt"
	"strconv"
)

func main() {
	var str2 string = "123456789"
	var n1 int64
	n1, _ = strconv.ParseInt(str2,36,64) //  这里只能转化为  64 位的,即  int64
	fmt.Printf("n1 type %T, n1=%v\n",n1,n1)
	
	i, _ := strconv.ParseInt("23", 5, 64)
	println(i)    // 13
}

打印输出:

$ go run main.go
b type bool b =true
n1 type int64, n1=2984619134745
13

注意事项:

如果给定的字符串没有成功转化为整数,Glang 会将它转化为 0【对应数据类型的默认值】。

package main

import (
	"fmt"
	"strconv"
)
func main() {
	// 注意事项:
	var str4 string = "hello"
	var n3 int64 = 11
	n3, _ = strconv.ParseInt(str4, 10, 64)
	fmt.Printf("n3 type is %T, n3 = %v", n3, n3)
}

值类型和引用类型

值类型:变量直接存储值,内存通常在栈中分配

  • int系列,float系列,bool,string,数组和结构体【struct】。

示意图:

sX9UYA

引用类型:变量存储的是-个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。

  • 指针、slice 切片、map、管道 chan,interface 等都是引用类型。
znzMDA

内存的展区和堆区示意图:

olbRQs

GO 的命名规范

1. 标识符的命名规则

  1. 由26个英文子母大小与,0-9,_ 组成
  2. 数字不可以开头。var num int //ok var 3num int //error
  3. Golang中严格区分大小写。
  4. 标识符不能包含空格。
  5. 下划线 "_ " 本身在Go中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用。
  6. 不能以系统保留关键字作为标识符【共 25 个】,比如break, if等等.

系统保留关键字

breakdefaultfuncinterfaceselect
casedefergomapstruct
clanelsegotopackageswitch
constFallthroughifrangetype
continueforimportreturnVar

预定义标识符

appendboolbytecapclosecomplex
complex64complex128uint16copyfalsefloat32
float64imagintint8int16uint32
Int32int64iodalenmakenew
nilpanicuint64printprintlnreal
revoverstringtrueuintuint8uintprt

2. 标识符命名注意事项

  • 包名:保持 package 的名字和目录保持一致,尽量采取有意义的包名,简短,有意义,不要和标准库不要冲突,例如 fmt。
CuD5fG
  • 变量名、函数名、常量名,采用驼峰法

举例:

var stuName string = "tom"
var goodPrice float32 = 1234.5
  • 如果变量名、函数名、常量名首字母大写,则可以被其他的包访问:如果首字母小写,则只能在本包中使用( 注:可以简单的理解成,首字母大写是公开的,首字母小写是私有的) ,在 golang 没有 public ,private 等关键字。

Go 运算符

算数运算符

主要有如下运算法:

  1. +、-、*、/、%、++、- -。

  2. 自增 ++

  3. 自减 –

这里比较奇葩的就是 %【模,或者说是取余】:

在正数的情况下,取模运算就是按正常去取余数即可。但是在负数的情况下,情况就会有所区别了。

需要遵循这个公式:a % b = a - (a / b) * b

package main

import (
	"fmt"
)

func main() {
	// 演示 % 的使用
	// 看一个公式 a % b = a - (a / b) * b
	fmt.Println("10%3=", 10 % 3)
	fmt.Println("-10%3=", -10 % 3) // -10 - (10) / 3 = -10 - (-9)
	fmt.Println("10%-3=", 10 % -3)
	fmt.Println("-10%-3=", -10 % -3)  // =?
}

打印输出:

10  %  3 =  1
-10 %  3 = -1
10  % -3 =  1
-10 % -3 = -1

当然此时我们打开 Python Shell,会发现结果和 Go 里面有所区别:

nkmvSC
  1. 对于除号"", 它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃
    小数部分。例如: x=19/5.结果是 3
  2. 当对一个数取模时,可以等价a%b=a-ab*b ,这样我们可以看到取模的一个本质运算。
  1. Golang 的自增自减只能当做一个独立语言使用时,不能用于赋值或者条件语句:
package main

import "fmt"

func main() {
	var i int = 8
	var a int
	i++ 
	//a = i++  // 错误
	b = i--
}

关系运算符

  1. 关系运算符的结果都是 bool 型,要么是 true,要么是 false
  2. 关系表达式经常用在 if 结构的条件中或 循环结构 的条件中。
package main
import (
	"fmt"
)

func main()  {
	// 演示关系运算符的使用˜
	var n1 int = 9
	var n2 int = 8
	fmt.Println(n1 == n2)    // false
	fmt.Println(n1 != n2)	 // true
	fmt.Println(n1 > n2)	 // true
	fmt.Println(n1 >= n2)	 // true
	fmt.Println(n1 <= n2)	 // false
	flag := n1 > n2
	fmt.Println("flag=", flag)

}

打印输出:

false
true
true
true
false
flag= true

逻辑运算符

这里和 Python 类比,需要关注的就是它 需要加上俩花括号{}

package main

import "fmt"

func main()  {
	var age int = 40

	// &&
	if age > 30 && age < 50{
		fmt.Println("ok1")
	}

	if age > 30 && age < 40 {
		fmt.Println("ok2")
	}

	// ||
	if age > 30 || age < 50{
		fmt.Println("ok3")
	}
	if age > 30 || age < 40{
		fmt.Println("ok4")
	}

	// !
	if age > 30 {
		fmt.Println("ok5")
	}
	if !(age > 30){
		fmt.Println("ok6")
	}
}

打印输出:

ok1
ok3
ok4
ok5

赋值运算符

运算符描述实例
=简单的赋值运算,将一个表达式的值赋给一个左值C=A+B 将 A+B 表达式结果赋给 C
+=相加后赋值C += A 等价于 C = C + A
-=相减后赋值C -= A 等价于 C = C - A
*=相乘后赋值C *= A 等价于 C = C * A
/=相除后赋值C /= A 等价于 C = C / A
%=求余后赋值C %= A 等价于 C = C % A
package main

import "fmt"

func main() {

	// 赋值运算符的使用演示
	// var i int
	// i = 10 //  基本赋值

	// 有两个变量,a 和 b,要求将其进行交换,最终打印结果
	// a = 9, b = 2 ==> a =2 b = 9
	a := 9
	b := 2
	fmt.Printf("交换前的情况是: a = %v, b = %v\n", a, b)

	a, b = b, a
	fmt.Printf("交换后的情况是: a = %v, b = %v\n", a, b)

	// 复合赋值的操作
	a += 17
	fmt.Println("a=", a)
}

打印输出:

交换前的情况是: a = 9, b = 2
交换后的情况是: a = 2, b = 9
a= 19

另一种交换变量的方法:

package main

import "fmt"

func main()  {
	
	var a int = 10
	var b int = 20

	a = a + b
	b = a - b    // 此时 b 里面存放的值是 a
	a = a - b    
	fmt.Printf("a = %v, b = %v", a, b)
}

打印输出:

a = 20, b = 10

运算符的优先级

LISNl0

位运算符

运算符描述实例
&按位与运算符"&"是双目运算符。
其功能是参与运算的两数各对应的二进位相与。
(A & B) 结果为 12, 二进制为 0000 1100
|按位或运算符"|"是双目运算符。
其功能是参与运算的两数各对应的二进位相或
(A | B) 结果为 61, 二进制为 0011 1101
^按位异或运算符"^"是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。(A ^ B) 结果为 49, 二进制为 0011 0001
<<左移运算符"<<“是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<“左边的运算数的各二进位全部左移若干位,由”<<"右边的数指定移动的位数,高位丢弃,低位补0。A << 2 结果为 240 ,二进制为 1111 0000
>>右移运算符">>“是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>“左边的运算数的各二进位全部右移若干位,”>>"右边的数指定移动的位数。A >> 2 结果为 15 ,二进制为 0000 1111
  • Golang中有2个移位运算符:

>>、<<右移和左移,运算规则:

右移运算符>>:低位溢出,符号位不变并用符号位补溢出的高位

左移运算符<<:符号位不变,低位补0

其他运算符

运算符描述实例
&返回变量存储地址&a,将给出变量的实际地址
*指针变量*a;是一个指针变量
package main

import "fmt"

func main() {
	a := 100
	var b *int = &a
	fmt.Printf("a的地址是:%v\n", &a)
	fmt.Printf("a的值是:%v", *b)
}

打印输出:

a的地址是:0x1400011c000
a的值是:100

注意:Golang 中没有三元运算符!

杂项

控制台的输入输出

  • fmt.Scanln(地址)
package main

import "fmt"

func main() {
	var name string
	var age byte
	var sal float32
	var isPass bool

	// 方式1: fmt.Scanln
	fmt.Printf("请输入姓名")
	fmt.Scanln(name)

	fmt.Printf("请输入年龄")
	fmt.Scanln(&age)

	fmt.Printf("请输入薪水")
	fmt.Scanln(&sal)

	fmt.Printf("请输入是否通过考试")
	fmt.Scanln(&isPass)

	fmt.Printf("名字是 %v,\n 年龄是 %v, \n 薪税是 %v, \n 是否通过考试 %v\n", name, age, sal, isPass)
}

打印输出:

请输入姓名xzc
请输入年龄12
请输入薪水23.2
请输入是否通过考试true
名字是 xzc,
年龄是 12, 
薪税是 23.2, 
是否通过考试 true
  • fmt.Scanf(地址符)
package main

import "fmt"

func main() {
	var name string
	var age byte
	var sal float32
	var isPass bool

	// 方式二: fmt.Scanf
	fmt.Println("请输入您的姓名,年龄,薪水,是否通过考试,使用空格隔开")
	fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
	fmt.Printf("名字是 %v, \n年龄是 %v, \n 薪水是 %v,\n 是否通过考试%v\n",name, age,sal, isPass)
}

$ go run main.go
请输入您的姓名,年龄,薪水,是否通过考试,使用空格隔开
xzc 19 34543.1 false
名字是 xzc, 
年龄是 19, 
薪水是 34543.1,
是否通过考试false

原码、反码、补码

对于有符号的而言:

  1. 二进制的最高位是符号位: 0表示正数,1表示负数
  • 1==> [0000 0001] -1===>[1000 0001]
  1. 正数的原码,反码,补码都一样

  2. 负数的反码=它的原码符号位不变,其它位取反(0->1,1->0)

  • 1===>原码 [0000 0001] 反码[0000 0001] 补码[0000 0001]
  • -1===>原码 [1000 0001] 反码[11111110] 补码[1111 1111]
  1. 负数的补码 = 它的反码 + 1
  1. 0 的反码,补码都是 0
  2. 计算机运算的时候,都是以补码的方式来运算的.
package main

import "fmt"

func main()  {
	fmt.Println(2&3)	// 2
	fmt.Println(2|3)	// 3
	fmt.Println(2^3)	// 3
	fmt.Println(-2^2)	// -4
}

下一篇:Golang 基础

参考内容:尚硅谷

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值