目录
前言
前期准备
环境配置:
Hello World!
一、基本变量
1.1 声明变量
1.2 初始化变量
1.3 变量声明到初始化的过程
1.4 变量值交换
1.5 匿名变量
1.6 变量的作用域
二、数据类型
1.1 整型
1.2 浮点型
1.3 字符串
1.4 布尔类型
1.5 数据类型判断
1.6 数据类型转换
1.7 指针类型(Pointer)
前言
欢迎你踏上学习Go编程语言的旅程!本章节将为你介绍Go语言的基本变量与数据类型,这是掌握Go编程的重要基础。Go语言以其简洁、高效和强大的特性而闻名。作为一个静态类型语言,Go在编译时就能捕获许多常见错误,这使得开发更加安全和高效。同时,Go的语法设计简洁明了,使得学习曲线相对平缓,非常适合初学者入门。
在接下来的内容中,将深入探讨Go语言的基本变量类型,如整数、浮点数、布尔值和字符串等。你将学习如何声明变量、理解变量的作用域,以及如何在程序中使用这些基本类型。还会介绍Go语言特有的一些类型,帮助你全面了解Go的类型系统。理解并掌握这些基本概念对于你未来编写高质量的Go程序至关重要。它们是构建更复杂程序的基石,也是理解Go语言设计哲学的窗口。
记住,编程是一项实践性很强的技能。在学习过程中,我强烈建议你动手实践每个概念,编写并运行示例代码。这不仅能帮助你更好地理解理论知识,还能培养你的编程直觉。
让我们一起开始这段激动人心的Go语言学习之旅吧!相信通过本章节的学习,你将为成为一名优秀的Go程序员奠定坚实的基础。
前期准备
编辑器 Visual Studio Code,VScode的GO拓展
环境配置:
- 下载新版Go
- 访问官方下载页面:https://golang.org/dl/
- 选择适合您Windows版本的最新稳定版本(通常是 .msi 文件)
- 安装Go
- 双击下载的 .msi 文件
- 按照安装向导的提示进行操作
- 建议使用默认安装路径(
C:\Go
)
- 检查安装
如果安装错误,使用Windows卸载程序
- 打开控制面板,点击"卸载程序"
- 找到"Go Programming Language",右键点击并选择"卸载"
学习资源:BiliBili,书籍,go官方文档,Blog,Github
下面开始写你的第一个go代码
Hello World!
- Go 程序结构:
package main
声明这是主程序包import "fmt"
导入格式化输出的包func main() { ... }
定义程序的入口函数
- 程序内容:
fmt.Println("Hello, World!")
打印 "Hello, World!" 到控制台
package mainimport "fmt"
/*
这是多行注释的演示,下面演示单行注释
*///注意 "{" 不能单独放在一行,代码在运行时会产生错误:
func main() {fmt.Println("Hello, World!") //注意P要大写
}
- 运行方式:
go run hello.go
直接编译并运行程序
$ go run hello.go
Hello, World!
go build hello.go
编译生成可执行文件./hello.exe
运行编译后的可执行文件
$ go build hello.go
$ ls
hello.exe hello.go
$ ./hello.exe
Hello, World!
一、基本变量
变量是用来存储用户数据的,而不同的变量类型是用来存储不同的用户数据的。
编程语言中常见的数据类型有整型、浮点型、布尔型和结构体等。Go语言每个变量都有自己的类型,在使用它们前,必须经过声明。
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。与大部分语言相似
但是你都可以拿汉字当变量,想知道为什么吗?往后学习吧
package mainimport "fmt"func main() {我 := "iron tom"fmt.Println(我)
}
//result
iron tom
1.1 声明变量
- 一般格式:使用
var
关键字声明变量,后跟变量名和类型。如var a int
。 - 批量格式:可以将多个变量声明组合在一起,提高可读性。
var (a intb stringc bool
)
Go在声明变量时,会自动对每个变量的内存区域进行初始化,每个变量会有其对应默认值:
- 未初始化的变量会被赋予零值(整型为0,字符串为"",布尔型为false)。
- 切片、映射、函数和指针变量默认为nil。
nil相当于其他语言中的null、None、NULL等,指代零值。
1.2 初始化变量
可以在声明时或之后给变量赋值。
- 一般格式:var 变量名 变量类型 = 表达式,如:var a int = 1。
- 短变量声明:使用
:=
可以同时声明和初始化变量,如d := 20.5
。 - 多变量声明:可以一次声明多个变量,如
var e, f int = 30, 40
。 - 类型推断:在初始化时可以省略类型。(最后会与数据类型例子结合展示)
package mainimport "fmt"func main() {var name stringname := "Tom"fmt.Println(name) }
在开发中,短变量声明是使用最为普遍的,但是在使用的过程中需要注意变量重复声明问题
//result # command-line-arguments .\demo.go:9:7: no new variables on left side of :=
1.3 变量声明到初始化的过程
- 变量声明的核心目的是在内存中分配空间并为这个空间创建一个引用。
- 变量初始化是在声明后(有时与声明同时)为变量赋予初始值的过程。
package mainimport ("fmt""unsafe"
)func main() {// 变量声明var a int// 打印a的内存地址和初始(零)值fmt.Printf("After declaration:\n")fmt.Printf("a's address: %p\n", &a)fmt.Printf("a's value: %d\n", a)fmt.Printf("a's size in bytes: %d\n\n", unsafe.Sizeof(a))// 变量初始化a = 42// 再次打印a的内存地址和新值fmt.Printf("After initialization:\n")fmt.Printf("a's address: %p\n", &a)fmt.Printf("a's value: %d\n", a)
}
变量声明后,内存已分配(我们可以打印地址),并且被初始化为零值。
初始化改变了内存位置的内容,但地址保持不变。
我们可以看到分配给int类型变量的内存大小。
After declaration:
a's address: 0xc00000a0b8
a's value: 0
a's size in bytes: 8After initialization:
a's address: 0xc00000a0b8
a's value: 42
1.4 变量值交换
- 通过中间值交换
package mainimport "fmt"func main() {// 声明并初始化两个变量a := 5b := 10// 打印原始值fmt.Println("Before swap: a =", a, "b =", b)// 使用多重赋值语句交换两个变量的值a, b = b, a// 打印交换后的值fmt.Println("After swap: a =", a, "b =", b)
}
//result
Before swap: a = 5 b = 10
After swap: a = 10 b = 5
- 直接交换
package mainimport "fmt"func main() {// 声明并初始化两个变量a := 5b := 10// 打印原始值fmt.Println("Before swap: a =", a, "b =", b)// 使用多重赋值语句交换两个变量的值a, b = b, a// 打印交换后的值fmt.Println("After swap: a =", a, "b =", b)
}
//result
Before swap: a = 5 b = 10
After swap: a = 10 b = 5
1.5 匿名变量
在赋值给多个变量时,如果存在不需要接受值的变量,可以使用匿名变量来代替。
由于匿名不占用匿名空间,也不会分配系统内存,匿名变量与匿名变量之间不会因为多次声明而无法使用。
package mainimport "fmt"// 函数返回两个整数值
func ReturnData() (int, int) {return 1, 2
}func main() {a, _ := ReturnData() //a 接受第一个返回值,匿名变量接受第二个返回值_, b := ReturnData() //匿名变量接受第一个返回值,b 接受第二个返回值fmt.Println(a, b)
}
//result
1, 2
1.6 变量的作用域
变量的作用域是指变量在程序中可以被访问的范围。在Go语言中,变量的作用域主要分为以下几种:局部作用域(函数内部)、包级作用域、全局作用域
- 包级作用域:
packageVar
是在函数外声明的,属于包级作用域。- 它可以在整个包(这个例子中就是 main 包)的任何地方被访问。
- 局部作用域(函数级):
localVar
是在main
函数内声明的,只能在main
函数内访问。funcVar
是在exampleFunction
内声明的,只能在该函数内访问。
- 代码块作用域:
blockVar
是在main
函数内的一个代码块中声明的,只能在该代码块内访问。
package mainimport "fmt"// 包级变量,在整个包内可访问
var packageVar = "我是包级变量"func main() {// 局部变量,只在main函数内可访问localVar := "我是局部变量"fmt.Println(packageVar) // 可以访问包级变量fmt.Println(localVar) // 可以访问局部变量// 演示代码块作用域{blockVar := "我是代码块变量"fmt.Println(blockVar)}// fmt.Println(blockVar) // 这行如果取消注释会报错,因为blockVar在这里不可访问demo()// fmt.Println(funcVar) // 这行如果取消注释会报错,因为funcVar只在exampleFunction中可访问
}func demo() {funcVar := "我是函数内的变量"fmt.Println(funcVar)fmt.Println(packageVar) // 可以访问包级变量// fmt.Println(localVar) // 这行如果取消注释会报错,因为localVar只在main函数中可访问
}
//result
我是包级变量
我是局部变量
我是代码块变量
我是函数内的变量
我是包级变量
需要注意的几点:
- 在Go中,花括号
{}
定义了一个新的作用域。 - 内部作用域可以访问外部作用域的变量,但外部作用域不能访问内部作用域的变量。
- 如果在不同作用域中声明了同名变量,内部作用域的变量会"遮蔽"外部作用域的变量。
- Go语言不支持全局变量的概念,但包级变量在很多情况下可以起到类似的作用。
- 使用
:=
声明的变量只能在函数内部使用,不能用于声明包级变量。
二、数据类型
Go语言的数据类型十分丰富,常见的包括整型、浮点型、字符串和布尔型等。Go语言特有的数据类型包括接口和通道等。
1.1 整型
- 有符号整型:
- int: 依据系统架构,在32位系统上为32位,在64位系统上为64位
- int8: 8位有符号整数,范围 -128 到 127
- int16: 16位有符号整数,范围 -32,768 到 32,767
- int32: 32位有符号整数,范围 -2,147,483,648 到 2,147,483,647
- int64: 64位有符号整数,范围 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
- 无符号整型:
- uint: 依据系统架构,在32位系统上为32位,在64位系统上为64位
- uint8: 8位无符号整数,范围 0 到 255
- uint16: 16位无符号整数,范围 0 到 65,535
- uint32: 32位无符号整数,范围 0 到 4,294,967,295
- uint64: 64位无符号整数,范围 0 到 18,446,744,073,709,551,615
- 特殊整型:
- byte: uint8 的别名,用于强调值是一个原始的字节
- rune: int32 的别名,用于表示一个 Unicode 码点
- uintptr: 无符号整型,大小足以存储指针值的未解释位
package mainimport "fmt"func main() {a := 3b := 2c := a / bfmt.Println(c)
}
//result
1
注意:在Go语言中,对于两个整型变量的除法运算,小数部分将会直接被截取,只取整数部分,不会存在四舍五入的情况。
1.2 浮点型
Go语言提供了两种浮点数类型:float32 和 float64。这两种类型遵循 IEEE-754 标准,分别为单精度和双精度浮点数。
- float32(单精度浮点数):
- 32位,4字节
- 精度大约为6-9位十进制数字
- 范围大约为 ±1.18 × 10^-38 到 ±3.4 × 10^38
- float64(双精度浮点数):
- 64位,8字节
- 精度大约为15-17位十进制数字
- 范围大约为 ±2.23 × 10^-308 到 ±1.80 × 10^308
package mainimport ("fmt""reflect"
)func main() {a := 3.0b := 2.0c := a / bfmt.Println(c)fmt.Println(reflect.TypeOf(c))fmt.Println(reflect.TypeOf(a))fmt.Println(reflect.TypeOf(b))
}
//result
1.5
float64
float64
float64
1.3 字符串
字符&转义字符
在Go中,单个字符通常用rune (int32)类型表示,用于表示一个 Unicode 码点(可以说对任何字符转ASCII码,汉字也是可以的)。而转义字符则是用来在字符串中表示特殊字符或不可打印字符的方式。
- 常用转义字符:
\n
: 换行\t
: 制表符\r
: 回车\"
: 双引号\\
: 反斜杠package mainimport "fmt"func main() {str1 := '我'str2 := 'w'fmt.Println(str1, str2) } //result 25105 105
注意:单引号与双引号的区别,单引号只能输入一个字符且返还的结果是对应的ASCII码。
- 基本定义:
- 在Go中,字符串是一个不可变的字节序列。
- 字符串可以包含任意数据,包括字节值0,但通常用于存储人类可读的文本。
- UTF-8 编码:Go字符串默认使用UTF-8编码,可以方便地处理Unicode字符。(这就是为什么变量都可以使用汉字的原因了)
- 声明和初始化:
- 可以使用双引号 "" 或反引号 ``(多行表示) 来创建字符串。
- 双引号创建的字符串可以包含转义字符。
- 反引号创建的字符串是原始字面量,不处理任何转义字符,可以跨多行。
package mainimport "fmt"func main() {// ``示例str := `这是第一行
这是第二行
这是第三行`fmt.Println(str)
}
//result
这是第一行
这是第二行
这是第三行
- 字符串连接:可以使用 + 运算符。
package mainimport "fmt"func main() {str1 := "Hello, "str2 := "World!"fmt.Println(str1 + str2)
}
//result
Hello, World!
- 字符串查找和替换
package mainimport ("fmt""strings"
)func main() {str := "Go语言是一个高效的编程语言"// 查找子串index := strings.Index(str, "高效")fmt.Printf("'高效' 的索引位置: %d\n", index)// 替换子串newStr := strings.Replace(str, "高效", "强大", 1)fmt.Printf("替换后的字符串: %s\n", newStr)// 统计子串出现次数count := strings.Count(str, "语言")fmt.Printf("'语言' 出现的次数: %d\n", count)
}
//result
'高效' 的索引位置: 11
替换后的字符串: Go语言是一个强大的编程语言
'语言' 出现的次数: 2
- 字符串分割和连接
package mainimport ("fmt""strings"
)func main() {// 分割字符串str := "Go,Python,Java,C++"languages := strings.Split(str, ",")fmt.Println("分割后的切片:")for i, lang := range languages {fmt.Printf("%d: %s\n", i, lang)}// 连接字符串newStr := strings.Join(languages, " | ")fmt.Printf("连接后的字符串: %s\n", newStr)// 去除首尾空白spaceStr := " Go language "trimmed := strings.TrimSpace(spaceStr)fmt.Printf("去除空白后: '%s'\n", trimmed)
}
//result
分割后的切片:
0: Go
1: Python
2: Java
3: C++
连接后的字符串: Go | Python | Java | C++
去除空白后: 'Go language'
1.4 布尔类型
布尔型是最简单的数据类型,只有两个值:false(假)和true(真)。
package mainimport "fmt"func main() {var a boolfmt.Println(a)a = truefmt.Println(a)
}
//result
false
true
1.5 数据类型判断
如果我们需要判断变量的类型,可以使用Go语言标准库中的reflect包,通过将变量传入此包的 TypeOf()方法,得到变量的数据类型。
package mainimport ("fmt""reflect"
)func main() {a := 1b := "test"c := true//TypeOf()方法直接返回传入变量的类型,通过Println()方法打印到控制台。fmt.Println(reflect.TypeOf(a))fmt.Println(reflect.TypeOf(b))fmt.Println(reflect.TypeOf(c))
}
//result
int
string
bool
1.6 数据类型转换
Go是静态类型的,变量在声明时就确定了类型。Go语言常见的数据类型之间能够互相进行类型转换,通过使用类型前置加小括号的方式进行。
package mainimport "fmt"func main() {// 整数与浮点数之间的转换var f float64 = 3.14var i int32 = 100i = int32(f)fmt.Printf("float64 to int32: %v (%T)\n", i, i)
}
//result
float64 to int32: 3 (int32)
静态类型语言 VS 动态类型语言
静态类型语言 动态类型语言 类型错误在编译时就能被发现 变量类型可以在运行时改变 通常运行速度更快 更加灵活,开发速度可能更快 代码通常更易于理解和维护 某些类型错误只能在运行时被发现 静态类型语言在编译时进行类型检查。这意味着变量的类型在编译时就已经确定,并且不能在运行时改变。例子:Go、Java、C++、Rust
// Go (静态类型) package mainimport "fmt"func main() {var x int = 5// x = "hello" // 编译错误:不能将string类型赋值给int类型fmt.Println(x)// y := 10 // 类型推断// y = "world" // 编译错误:不能将string类型赋值给int类型 }
与静态类型相对,动态类型语言在运行时进行类型检查。例子:Python、JavaScript、Ruby
# Python (动态类型) x = 5 print(x) x = "hello" # 正常运行:x的类型从int变为str print(x)y = 10 y = "world" # 正常运行:y的类型从int变为str print(y)
Go 倾向于明确和类型安全,而 python 提供了更大的灵活性,但代价是可能出现意外的行为。
显式 vs 隐式
- Go 要求大多数类型转换是显式的,如
int64(i)
或strconv.Atoi(s)
。- JavaScript 允许许多隐式转换,如
"3" + 2
或"3" - 2
。
1.7 指针类型(Pointer)
谈及指针,很多人可能马上会联想到C/C++中的指针,指针的存在是C/C++强大的根本所在, 但同时也带来很多安全问题,相比之下Go语言的指针则更加高效和安全。
- 声明指针
指针是一种地址值,这个地址值代表着计算机内存空间中的某个位置。指针变量就是存放地 址值的变量,指针变量的声明格式如下:
var 变量名 *int
一般情况下,我们将指针变量的类型声明为*int,变量名为“p”开头(指代“point”)的单 词,如“p”或“ptr”。
package mainimport "fmt"func main() {var p *intfmt.Println(p)
}
//result
<nil> //由于指针变量未指向任何地址,所以打印值为nil。
- 取变量地址 :Go语言中,使用操作符“&”取变量地址,取得的地址值可以赋给指针变量。由于指针变量本身也是变量,因此指针变量在计算机内存中也有自己的地址。
- 获取指针所指向的内容:指针变量存储的值为地址值,通过在指针变量前面加上“*”符号可以获取指针所指向地址值 的内容。
package mainimport "fmt"func main() {num := 1var p *intp = &numfmt.Println("num变量的地址", p)fmt.Println("指针变量p的值", &p)fmt.Println("指针变量p所指向的值", *p)
}
//result
num变量的地址 0xc00000a0b8
指针变量p的值 0xc00006c028
指针变量p所指向的值 1
注意:p指针声明后其值为nil,这时如果获取指针p指向的地址内容,则会出错
package mainimport "fmt"func main() {var p *intfmt.Println("指针变量p指向的地址为:", p)fmt.Println("指针变量p所指向的内容:", *p)
}
//result
指针变量p指向的地址为: <nil>
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x0 addr=0x0 pc=0xbb95cb]
- 使用指针修改值
在指针变量有实际指向的地址值后,可以通过如下格式直接修改指针所指向内存地址的内容:
*变量名 = 修改值
package mainimport "fmt"func main() {num := 1var p *intp = &numfmt.Println("指针变量p所指向的内容:", *p)*p = 10fmt.Println("指针变量p所指向的内容:", *p)
}
//result
指针变量p所指向的内容: 1
指针变量p所指向的内容: 10
也可使用new()函数来给指针分配地址并初始化地址对应的值
package mainimport "fmt"func main() {var p *intp = new(int)fmt.Println("指针变量p所指向的内容:", *p)*p = 10fmt.Println("指针变量p所指向的内容:", *p)
}
//result
指针变量p所指向的内容: 0
指针变量p所指向的内容: 10