Go语言的使用
✨ Go的基本语法
1. 基本数据类型
在 Go 语言中,不同数据类型占用的内存大小是固定的
数据类型 | 占用内存大小 | 描述 |
---|---|---|
bool | 1字节 | 布尔值,true 或 false |
int8 | 1字节 | 8位有符号整数,范围 -128 到 127 |
uint8 | 1字节 | 8位无符号整数,范围 0 到 255 |
int16 | 2字节 | 16位有符号整数,范围 -32768 到 32767 |
uint16 | 2字节 | 16位无符号整数,范围 0 到 65535 |
int32 | 4字节 | 32位有符号整数,范围 -2147483648 到 2147483647 |
uint32 | 4字节 | 32位无符号整数,范围 0 到 4294967295 |
int64 | 8字节 | 64位有符号整数,范围 -9223372036854775808 到 9223372036854775807 |
uint64 | 8字节 | 64位无符号整数,范围 0 到 18446744073709551615 |
int | 平台相关 | 通常为 int32 或 int64 |
uint | 平台相关 | 通常为 uint32 或 uint64 |
uintptr | 平台相关 | 通常为 uint32 或 uint64 ,用于存储指针值 |
float32 | 4字节 | 32位浮点数 |
float64 | 8字节 | 64位浮点数 |
complex64 | 8字节 | 两个 float32 组成的复数 |
complex128 | 16字节 | 两个 float64 组成的复数 |
byte | 1字节 | uint8 的别名 |
rune | 4字节 | int32 的别名,用于表示 Unicode 码点 |
复合数据类型
数据类型 | 占用内存大小 | 描述 |
---|---|---|
string | 变长 | 字符串,存储实际字符的字节数加上一些额外的元数据(如长度) |
[]T | 变长 | 切片,包含指向底层数组的指针、长度和容量 |
[n]T | 固定 | 数组,大小为 n * sizeof(T) |
map[K]V | 变长 | 映射,包含哈希表的元数据和实际存储的数据 |
struct | 固定 | 结构体,大小为各字段大小之和,可能包含填充字节以对齐 |
chan T | 变长 | 通道,包含通道的元数据和缓冲区(如果有) |
interface{} | 变长 | 接口,包含类型信息和值指针 |
*T | 平台相关 | 指针,通常为 4 字节或 8 字节 |
package main
import "fmt"
func main() {
var i int = 42
var f float64 = float64(i) // 类型转换
var u uint = uint(f)
fmt.Printf("i: %d, f: %f, u: %d\n", i, f, u)
}
fmt
包格式化选项
fmt
包的格式化选项整理成表格形式,以便更清晰地查看和理解。以下是一个详细的表格:
格式化符 | 描述 | 示例输入 | 示例输出 |
---|---|---|---|
%d | 十进制整数 | 123 | 123 |
%x | 十六进制整数 | 123 | 7b |
%o | 八进制整数 | 123 | 173 |
%b | 二进制整数 | 123 | 1111011 |
%c | 对应整数的 Unicode 字符 | 123 | { |
%f | 浮点数,小数点后默认6位 | 123.456 | 123.456000 |
%e | 科学记数法,小数点前一位,小数点后默认6位 | 123.456 | 1.234560e+02 |
%E | 科学记数法,小数点前一位,小数点后默认6位,使用大写E | 123.456 | 1.234560E+02 |
%g | 根据数值大小自动选择 %e 或 %f ,去掉尾部的零 | 123.456 | 123.456 |
%s | 字符串 | "hello" | hello |
%q | 带引号的字符串,适合打印字符串字面量 | "hello" | "hello" |
%t | 布尔值,true 或 false | true | true |
%p | 指针地址 | &s | 0xc0000160a0 |
%v | 默认格式,适用于任何类型 | 123 | 123 |
%T | 类型的名称 | 123 | int |
%% | 输出百分号本身 | %% | % |
%w | 指定最小宽度,不足时用空格填充 | %5d | 123 |
%.p | 指定精度,对于浮点数是小数点后的位数,对于字符串是最大长度 | %.2f | 123.46 |
%-w | 左对齐 | %-5d | 123 |
package main
import (
"fmt"
)
func main() {
// 整数
fmt.Printf("十进制: %d, 十六进制: %x, 八进制: %o, 二进制: %b, 字符: %c\n", 123, 123, 123, 123, 123)
// 浮点数
fmt.Printf("浮点数: %f, 科学记数法: %e, 科学记数法大写E: %E, 自动选择: %g\n", 123.456, 123.456, 123.456, 123.456)
// 字符串
fmt.Printf("字符串: %s, 带引号: %q\n", "hello", "hello")
// 布尔值
fmt.Printf("布尔值: %t\n", true)
// 指针
var s string = "hello"
fmt.Printf("指针地址: %p\n", &s)
// 宽度和精度
fmt.Printf("右对齐: %5d, 左对齐: %-5d, 小数点后两位: %.2f\n", 123, 123, 123.456)
// 其他选项
fmt.Printf("默认格式: %v, 类型名称: %T\n", 123, 123)
}
输出结果
十进制: 123, 十六进制: 7b, 八进制: 173, 二进制: 1111011, 字符: {
浮点数: 123.456000, 科学记数法: 1.234560e+02, 科学记数法大写E: 1.234560E+02, 自动选择: 123.456
字符串: hello, 带引号: "hello"
布尔值: true
指针地址: 0xc0000160a0
右对齐: 123, 左对齐: 123 , 小数点后两位: 123.46
默认格式: 123, 类型名称: int
2. 变量和常量
package main
import "fmt"
func main() {
// 变量声明
var a int = 10
var b = 20 // 自动推断类型
c := 30 // 简短声明(只能在函数内部使用)
// 常量
const pi = 3.14
fmt.Println("a:", a, "b:", b, "c:", c, "pi:", pi)
}
2.1 短声明(short variable declaration)是一种简洁的方式来声明并初始化变量。短声明使用 :=
操作符,语法如下:
// variable := value
package main
import "fmt"
func main() {
// 短声明
a := 42
b := "hello"
c := true
fmt.Println(a, b, c) // 输出: 42 hello true
}
多变量声明
package main
import "fmt"
func main() {
x, y := 10, 20
fmt.Println(x, y) // 输出: 10 20
}
函数返回值
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
// 初始化随机数生成器
rand.Seed(time.Now().UnixNano())
// 接收函数返回值
num := rand.Intn(100)
fmt.Println(num) // 输出一个 0 到 99 之间的随机数
}
-
作用域:短声明只能在函数内部使用,不能在包级别的声明中使用。
-
重复声明:在一个作用域内,同一个变量名不能被多次短声明。例如:
package main import "fmt" func main() { a := 42 a := 100 // 编译错误: cannot declare and initialize the same variable twice in the same block fmt.Println(a) }
但是,如果你已经声明了一个变量,可以在同一个作用域内重新赋值:
package main import "fmt" func main() { a := 42 a = 100 // 合法 fmt.Println(a) // 输出: 100 }
多变量赋值
package main
import "fmt"
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("division by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result) // 输出: Result: 5
}
}
短声明是 Go 语言中非常实用的特性,可以使代码更加简洁和易读。但在使用时需要注意作用域和重复声明的问题。
2.2 有符号整数的环绕
在编程中,整数环绕(Integer Wraparound)是指当一个整数变量超出其最大或最小值范围时,它的值会自动“环绕”到另一个极端值。这种现象通常发生在有符号整数和无符号整数之间。
有符号整数(如 int8
, int16
, int32
, int64
)在超过其最大值时会变为最小值,反之亦然。这是由于它们的内部表示方式(通常是二进制补码)。
示例
package main
import (
"fmt"
)
func main() {
var i8 int8 = 127 // int8 的最大值
fmt.Println("i8 before increment:", i8)
i8++
fmt.Println("i8 after increment:", i8) // 输出 -128
var i8Min int8 = -128 // int8 的最小值
fmt.Println("i8Min before decrement:", i8Min)
i8Min--
fmt.Println("i8Min after decrement:", i8Min) // 输出 127
}
无符号整数的环绕
无符号整数(如 uint8
, uint16
, uint32
, uint64
)在超过其最大值时会变为0,反之亦然。这是因为无符号整数没有负数表示,只有正数和0。
package main
import (
"fmt"
)
func main() {
var u8 uint8 = 255 // uint8 的最大值
fmt.Println("u8 before increment:", u8)
u8++
fmt.Println("u8 after increment:", u8) // 输出 0
var u8Zero uint8 = 0 // uint8 的最小值
fmt.Println("u8Zero before decrement:", u8Zero)
u8Zero--
fmt.Println("u8Zero after decrement:", u8Zero) // 输出 255
}
类型 | 最大值 | 最小值 | 环绕示例 |
---|---|---|---|
int8 | 127 | -128 | 127 + 1 = -128 -128 - 1 = 127 |
int16 | 32,767 | -32,768 | 32,767 + 1 = -32,768 -32,768 - 1 = 32,767 |
int32 | 2,147,483,647 | -2,147,483,648 | 2,147,483,647 + 1 = -2,147,483,648 -2,147,483,648 - 1 = 2,147,483,647 |
int64 | 9,223,372,036,854,775,807 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 + 1 = -9,223,372,036,854,775,808 -9,223,372,036,854,775,808 - 1 = 9,223,372,036,854,775,807 |
uint8 | 255 | 0 | 255 + 1 = 0 0 - 1 = 255 |
uint16 | 65,535 | 0 | 65,535 + 1 = 0 0 - 1 = 65,535 |
uint32 | 4,294,967,295 | 0 | 4,294,967,295 + 1 = 0 0 - 1 = 4,294,967,295 |
uint64 | 18,446,744,073,709,551,615 | 0 | 18,446,744,073,709,551,615 + 1 = 0 0 - 1 = 18,446,744,073,709,551,615 |
- 溢出检测:在关键应用中,应该注意整数溢出的问题,可以通过检查变量是否在预期范围内来避免潜在的错误。
- 编译器优化:某些编译器可能会优化掉显式的溢出检查,因此在编写代码时要特别小心。
- 安全编程:使用更大的整数类型(如
int32
或int64
)可以减少溢出的风险,但在某些情况下可能会增加内存使用。
3.2 math/big
包提供了用于高精度计算的整数、有理数和浮点数类型。
这些类型可以处理任意大小的数值,非常适合需要高精度计算的场景。以下是对 math/big
包中主要类型的介绍和使用示例。
big.Int
:用于表示任意大小的整数。big.Rat
:用于表示任意精度的有理数(分数)。big.Float
:用于表示任意精度的浮点数。
big.Int
的使用
big.Int
是最常用的类型,用于处理大整数。以下是一些基本操作的示例:
导入包
import "math/big"
big.Int
实例
// 通过 NewInt 创建一个 big.Int 实例
zero := big.NewInt(0)
one := big.NewInt(1)
// 通过 SetInt64 创建一个 big.Int 实例
largeNumber := big.NewInt(0).SetInt64(1234567890123456789)
// 通过 SetString 创建一个 big.Int 实例
veryLargeNumber, ok := new(big.Int).SetString("123456789012345678901234567890", 10)
if !ok {
panic("invalid number")
}
// 加法
sum := new(big.Int).Add(one, one) // sum = 2
// 减法
difference := new(big.Int).Sub(one, zero) // difference = 1
// 乘法
product := new(big.Int).Mul(one, largeNumber) // product = 1234567890123456789
// 除法
quotient := new(big.Int).Div(veryLargeNumber, largeNumber) // quotient = 100000000000000000000000000
// 模运算
remainder := new(big.Int).Mod(veryLargeNumber, largeNumber) // remainder = 0
// 幂运算
power := new(big.Int).Exp(largeNumber, big.NewInt(2), nil) // power = 1234567890123456789^2
// 比较
cmp := veryLargeNumber.Cmp(largeNumber) // cmp > 0 表示 veryLargeNumber > largeNumber
big.Rat
用于表示任意精度的有理数(分数)。
// 通过 NewRat 创建一个 big.Rat 实例
half := big.NewRat(1, 2)
// 通过 SetFloat64 创建一个 big.Rat 实例
piRat := new(big.Rat).SetFloat64(3.141592653589793)
// 通过 SetString 创建一个 big.Rat 实例
fraction, ok := new(big.Rat).SetString("1/3")
if !ok {
panic("invalid fraction")
}
// 加法
sumRat := new(big.Rat).Add(half, half) // sumRat = 1
// 减法
differenceRat := new(big.Rat).Sub(half, fraction) // differenceRat = 1/6
// 乘法
productRat := new(big.Rat).Mul(half, piRat) // productRat = 1.5707963267948965
// 除法
quotientRat := new(big.Rat).Quo(piRat, half) // quotientRat = 6.283185307179586
// 比较
cmpRat := piRat.Cmp(fraction) // cmpRat > 0 表示 piRat > fraction
big.Float
用于表示任意精度的浮点数。
// 通过 NewFloat 创建一个 big.Float 实例
pi := big.NewFloat(3.141592653589793)
// 通过 SetPrec 设置精度
pi.SetPrec(100) // 设置精度为 100 位
// 通过 SetString 创建一个 big.Float 实例
largeFloat, _, err := big.ParseFloat("12345678901234567890.12345678901234567890", 10, 0, big.ToNearestEven)
if err != nil {
panic("invalid float")
}
// 加法
sumFloat := new(big.Float).Add(pi, pi) // sumFloat = 6.283185307179586
// 减法
differenceFloat := new(big.Float).Sub(pi, pi) // differenceFloat = 0
// 乘法
productFloat := new(big.Float).Mul(pi, pi) // productFloat = 9.869604401089358
// 除法
quotientFloat := new(big.Float).Quo(pi, pi) // quotientFloat = 1
// 比较
cmpFloat := pi.Cmp(largeFloat) // cmpFloat < 0 表示 pi < largeFloat
math/big
包提供了强大的工具来处理大整数、有理数和浮点数,适用于需要高精度计算的场景。通过这些示例,您可以更好地理解和使用 math/big
包中的主要类型和方法。
2.4 Unicode 码点(Code Point)
可以使用不同的编码方式来表示。常见的 Unicode 编码方式包括 UTF-8、UTF-16 和 UTF-32。每种编码方式都有其特点和适用场景。以下是这三种编码方式的详细介绍:
UTF-8 是一种可变长度的编码方式,广泛用于互联网和现代操作系统中。它具有以下特点:
-
兼容 ASCII:对于 ASCII 字符(码点范围
U+0000
到U+007F
),UTF-8 编码与 ASCII 完全相同,使用 1 个字节。 -
可变长度:对于其他 Unicode 码点,UTF-8 使用 1 到 4 个字节来编码。
-
自同步:可以从任意位置开始解码,即使中间有错误也不会影响后续的解码。
-
1 字节:
0xxxxxxx
(U+0000
到U+007F
) -
2 字节:
110xxxxx 10xxxxxx
(U+0080
到U+07FF
) -
3 字节:
1110xxxx 10xxxxxx 10xxxxxx
(U+0800
到U+FFFF
) -
4 字节:
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
(U+10000
到U+10FFFF
)
UTF-8 示例
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Hello, 世界!"
for i, c := range str {
fmt.Printf("Index: %d, Rune: %U, Char: %c, UTF-8: % x\n", i, c, c, []byte(string(c)))
}
}
//Index: 0, Rune: U+0048, Char: H, UTF-8: 48
//Index: 1, Rune: U+0065, Char: e, UTF-8: 65
UTF-16 示例
package main
import (
"fmt"
"encoding/json"
)
func main() {
str := "Hello, 世界!"
// 将字符串转换为 UTF-16 编码
utf16 := json.RawMessage(str).EncodeUTF16()
for i, c := range utf16 {
fmt.Printf("Index: %d, UTF-16: %04x\n", i, c)
}
}
//Index: 0, UTF-16: 0048
//Index: 1, UTF-16: 0065
...
UTF-32 示例
package main
import (
"fmt"
)
func main() {
str := "Hello, 世界!"
// 将字符串转换为 UTF-32 编码
utf32 := []rune(str)
for i, c := range utf32 {
fmt.Printf("Index: %d, UTF-32: %08x\n", i, uint32(c))
}
}
//Index: 0, UTF-32: 00000048
//Index: 1, UTF-32: 00000065
...
- UTF-8:可变长度,兼容 ASCII,广泛用于互联网和现代操作系统。
- UTF-16:变长编码,主要用于 Windows 和 Java,对于 BMP 字符使用 2 个字节,对于辅助平面字符使用 4 个字节。
- UTF-32:固定长度,每个码点使用 4 个字节,编码和解码简单直接。
2.5 字符串是一种不可变的字节序列。
创建字符串
// 直接赋值
str1 := "Hello, World!"
// 使用反引号创建多行字符串
str2 := `This is a
multi-line
string.`
// 使用 fmt.Sprintf 创建格式化字符串
str3 := fmt.Sprintf("The answer is %d", 42)
字符串拼接
// 使用 + 运算符
str4 := "Hello" + " " + "World"
// 使用 fmt.Sprintf
str5 := fmt.Sprintf("%s %s", "Hello", "World")
// 使用 strings.Join
parts := []string{"Hello", "World"}
str6 := strings.Join(parts, " ")
字符串长度
length := len("Hello, World!") // length = 13
字符串索引
str := "Hello, World!"
firstChar := str[0] // 'H'
lastChar := str[len(str)-1] // '!'
子字符串
str := "Hello, World!"
subStr1 := str[0:5] // "Hello"
subStr2 := str[7:] // "World!"
subStr3 := str[:5] // "Hello"
subStr4 := str[len(str)-6:] // "World!"
字符串比较
str1 := "Hello"
str2 := "Hello"
str3 := "World"
isEqual := str1 == str2 // true
isNotEqual := str1 != str3 // true
字符串查找
str := "Hello, World!"
// 查找子字符串的位置
index := strings.Index(str, "World") // index = 7
// 查找子字符串是否存在
contains := strings.Contains(str, "World") // true
// 查找前缀
hasPrefix := strings.HasPrefix(str, "Hello") // true
// 查找后缀
hasSuffix := strings.HasSuffix(str, "World!") // true
字符串替换
str := "Hello, World!"
newStr := strings.Replace(str, "World", "Go", 1) // "Hello, Go!"
字符串分割
str := "a,b,c,d"
parts := strings.Split(str, ",") // []string{"a", "b", "c", "d"}
字符串修剪
str := " Hello, World! "
trimmed := strings.TrimSpace(str) // "Hello, World!"
字符串转换
str := "Hello, World!"
// 转换为小写
lower := strings.ToLower(str) // "hello, world!"
// 转换为大写
upper := strings.ToUpper(str) // "HELLO, WORLD!"
// 转换为驼峰式
camel := strings.Title(str) // "Hello, World!"
字符串格式化
// 使用 fmt.Sprintf 进行格式化
formatted := fmt.Sprintf("The answer is %d", 42) // "The answer is 42"
字符串遍历
str := "Hello, 世界!"
// 遍历每个字节
for i := 0; i < len(str); i++ {
fmt.Printf("%c ", str[i])
}
// 遍历每个 Unicode 码点
for i, c := range str {
fmt.Printf("Index: %d, Rune: %c\n", i, c)
}
3. 控制结构
条件语句
package main
import "fmt"
func main() {
age := 18
if age >= 18 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a minor.")
}
// switch语句
day := 2
switch day {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
default:
fmt.Println("Other day")
}
}
循环语句
Go语言中只有一种循环结构:for
。
package main
import "fmt"
func main() {
// 基本的for循环
for i := 0; i < 5; i++ {
fmt.Println(i)
}
// 类似while循环
count := 0
for count < 3 {
fmt.Println(count)
count++
}
// 遍历数组
arr := []string{"apple", "banana", "cherry"}
for index, value := range arr {
fmt.Printf("index: %d, value: %s\n", index, value)
}
}
4. 函数和多返回值
package main
import "fmt"
// 定义一个有返回值的函数
func add(x int, y int) int {
return x + y
}
// 多返回值函数
func divide(dividend, divisor int) (int, int) {
quotient := dividend / divisor
remainder := dividend % divisor
return quotient, remainder
}
func main() {
sum := add(10, 5)
fmt.Println("Sum:", sum)
quotient, remainder := divide(10, 3)
fmt.Printf("Quotient: %d, Remainder: %d\n", quotient, remainder)
}
5. 指针
package main
import "fmt"
func main() {
x := 10
ptr := &x // 获取变量的地址
fmt.Println("Address of x:", ptr)
fmt.Println("Value of x:", *ptr) // 通过指针访问变量值
}
6. 结构体与方法
package main
import "fmt"
// 定义一个结构体
type Person struct {
Name string
Age int
}
// 结构体方法
func (p Person) Greet() {
fmt.Printf("Hello, my name is %s and I am %d years old.\n", p.Name, p.Age)
}
func main() {
person := Person{Name: "Alice", Age: 25}
person.Greet()
}
7. 接口和多态
package main
import "fmt"
// 定义一个接口
type Animal interface {
Speak() string
}
// Dog类型实现了Animal接口
type Dog struct{}
func (d Dog) Speak() string {
return "Woof!"
}
// Cat类型实现了Animal接口
type Cat struct{}
func (c Cat) Speak() string {
return "Meow!"
}
func main() {
animals := []Animal{Dog{}, Cat{}}
for _, animal := range animals {
fmt.Println(animal.Speak())
}
}
8. 并发编程:goroutines 和 channels
package main
import (
"fmt"
"time"
)
// 函数,用于展示goroutine
func printMessage(message string) {
for i := 0; i < 3; i++ {
fmt.Println(message)
time.Sleep(500 * time.Millisecond)
}
}
func main() {
go printMessage("Hello from goroutine") // 启动goroutine
printMessage("Hello from main")
// 使用通道
ch := make(chan string)
go func() {
ch <- "Data sent to channel"
}()
msg := <-ch // 从通道接收数据
fmt.Println("Received:", msg)
}
9. 错误处理
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}
这些示例展示了Go语言的一些基本语法和特色特性。在实际开发中,还可以结合Go的标准库及第三方库来实现更多功能。