✨ Go的基本语法
基本数据类型
在 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 mainimport "fmt"func main() {var i int = 42var 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 mainimport ("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 mainimport "fmt"func main() {// 变量声明var a int = 10var b = 20 // 自动推断类型c := 30 // 简短声明(只能在函数内部使用)// 常量const pi = 3.14fmt.Println("a:", a, "b:", b, "c:", c, "pi:", pi)
}
短声明(short variable declaration)是一种简洁的方式来声明并初始化变量。短声明使用 :=
操作符,语法如下:
// variable := valuepackage mainimport "fmt"func main() {// 短声明a := 42b := "hello"c := truefmt.Println(a, b, c) // 输出: 42 hello true
}
多变量声明
package mainimport "fmt"func main() {x, y := 10, 20fmt.Println(x, y) // 输出: 10 20
}
函数返回值
package mainimport ("fmt""math/rand""time"
)func main() {// 初始化随机数生成器rand.Seed(time.Now().UnixNano())// 接收函数返回值num := rand.Intn(100)fmt.Println(num) // 输出一个 0 到 99 之间的随机数
}
-
作用域:短声明只能在函数内部使用,不能在包级别的声明中使用。
-
重复声明:在一个作用域内,同一个变量名不能被多次短声明。例如:
package mainimport "fmt"func main() {a := 42a := 100 // 编译错误: cannot declare and initialize the same variable twice in the same blockfmt.Println(a) }
但是,如果你已经声明了一个变量,可以在同一个作用域内重新赋值:
package mainimport "fmt"func main() {a := 42a = 100 // 合法fmt.Println(a) // 输出: 100 }
多变量赋值
package mainimport "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 语言中非常实用的特性,可以使代码更加简洁和易读。但在使用时需要注意作用域和重复声明的问题。
有符号整数的环绕
在编程中,整数环绕(Integer Wraparound)是指当一个整数变量超出其最大或最小值范围时,它的值会自动“环绕”到另一个极端值。这种现象通常发生在有符号整数和无符号整数之间。
有符号整数(如 int8
, int16
, int32
, int64
)在超过其最大值时会变为最小值,反之亦然。这是由于它们的内部表示方式(通常是二进制补码)。
示例
package mainimport ("fmt"
)func main() {var i8 int8 = 127 // int8 的最大值fmt.Println("i8 before increment:", i8)i8++fmt.Println("i8 after increment:", i8) // 输出 -128var 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 mainimport ("fmt"
)func main() {var u8 uint8 = 255 // uint8 的最大值fmt.Println("u8 before increment:", u8)u8++fmt.Println("u8 after increment:", u8) // 输出 0var 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
)可以减少溢出的风险,但在某些情况下可能会增加内存使用。
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
Unicode 码点(Code Point)
在 Go 语言中,Unicode 编码方式主要包括 UTF-8、UTF-16 和 UTF-32。Go 语言对这些编码方式提供了良好的支持,但最常用的是 UTF-8。下面分别介绍这三种编码方式在 Go 语言中的使用。
-
兼容 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
)
1. UTF-8
UTF-8 是 Go 语言中最常用的 Unicode 编码方式。Go 语言的字符串(string
)和字符(rune
)类型都默认使用 UTF-8 编码。
示例代码
package mainimport ("fmt""unicode/utf8"
)func main() {// UTF-8 编码的字符串str := "Hello, 世界"// 打印字符串fmt.Println("Original String:", str)// 遍历字符串,获取每个字符及其索引for i, ch := range str {fmt.Printf("Index: %d, Character: %c, UTF-8 Code Point: %U\n", i, ch, ch)}// 获取字符串的长度(字节数)byteLength := len(str)fmt.Println("Byte Length:", byteLength)// 获取字符串的长度(字符数)runeCount := utf8.RuneCountInString(str)fmt.Println("Rune Count:", runeCount)
}
2. UTF-16
UTF-16 编码在 Go 语言中不是默认使用的,但可以通过标准库中的 unicode/utf16
包进行处理。
示例代码
package mainimport ("fmt""unicode/utf16""encoding/binary"
)func main() {// UTF-8 编码的字符串str := "Hello, 世界"// 将 UTF-8 字符串转换为 UTF-16 编码utf16Rune := utf16.Encode([]rune(str))// 打印 UTF-16 编码的字节序列fmt.Println("UTF-16 Encoded:", utf16Rune)// 将 UTF-16 编码的字节序列转换回 UTF-8 字符串utf8Str := string(utf16.Decode(utf16Rune))fmt.Println("Decoded UTF-8 String:", utf8Str)// 将 UTF-16 编码的字节序列转换为字节切片var utf16Bytes []bytefor _, r := range utf16Rune {utf16Bytes = append(utf16Bytes, byte(r>>8), byte(r))}fmt.Println("UTF-16 Bytes:", utf16Bytes)// 将字节切片转换回 UTF-16 编码的字节序列var utf16RuneFromBytes []uint16for i := 0; i < len(utf16Bytes); i += 2 {r := binary.BigEndian.Uint16(utf16Bytes[i:])utf16RuneFromBytes = append(utf16RuneFromBytes, r)}fmt.Println("UTF-16 Rune From Bytes:", utf16RuneFromBytes)
}
- UTF-8 是 Go 语言中最常用的 Unicode 编码方式,字符串和字符类型默认使用 UTF-8 编码。
- UTF-16 和 UTF-32 编码可以通过
unicode/utf16
和unicode/utf32
包进行处理,但不是默认使用的编码方式。
字符串是一种不可变的字节序列。
创建字符串
// 直接赋值
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)
}
字符串类型转换
从字符串到其他类型的转换
-
字符串到整型的转换:可以使用
strconv
包中的Atoi
函数将字符串转换为整型。package mainimport ("fmt""strconv" )func main() {str := "42"num, err := strconv.Atoi(str) // 字符串转整型if err != nil {fmt.Println("Error:", err)} else {fmt.Println("String:", str, "Integer:", num)} }
-
字符串到浮点型的转换:可以使用
strconv
包中的ParseFloat
函数将字符串转换为浮点型。package mainimport ("fmt""strconv" )func main() {str := "3.14"num, err := strconv.ParseFloat(str, 64) // 字符串转浮点型if err != nil {fmt.Println("Error:", err)} else {fmt.Println("String:", str, "Float:", num)} }
-
其他类型到字符串的转换:可以使用
strconv
包中的FormatInt
、FormatFloat
等函数将其他类型转换为字符串。package mainimport ("fmt""strconv" )func main() {num := 42str := strconv.Itoa(num) // 整型转字符串fmt.Println("Integer:", num, "String:", str)f := 3.14strFloat := strconv.FormatFloat(f, 'f', -1, 64) // 浮点型转字符串fmt.Println("Float:", f, "String:", strFloat) }
2. 布尔类型转换
在Go语言中,布尔类型和字符串之间并没有直接的转换,布尔类型不能直接转化为字符串或反之。你需要手动实现这样的转换。
布尔到字符串的转换
可以使用fmt.Sprintf
或条件语句手动实现布尔值到字符串的转换。
package mainimport "fmt"func main() {b := truestr := fmt.Sprintf("%t", b) // 布尔转字符串fmt.Println("Boolean:", b, "String:", str)
}
字符串到布尔的转换
可以使用strconv
包中的ParseBool
函数将字符串转换为布尔值。注意,只有“true”或“false”会被识别为布尔值。
package mainimport ("fmt""strconv"
)func main() {strTrue := "true"strFalse := "false"strInvalid := "yes" // 这是一个无效的布尔字符串b1, err1 := strconv.ParseBool(strTrue) // 字符串转布尔if err1 != nil {fmt.Println("Error:", err1)} else {fmt.Println("String:", strTrue, "Boolean:", b1)}b2, err2 := strconv.ParseBool(strFalse) // 字符串转布尔if err2 != nil {fmt.Println("Error:", err2)} else {fmt.Println("String:", strFalse, "Boolean:", b2)}b3, err3 := strconv.ParseBool(strInvalid) // 无效的字符串if err3 != nil {fmt.Println("Error:", err3) // 输出错误信息} else {fmt.Println("String:", strInvalid, "Boolean:", b3)}
}
- 字符串转换:通过
strconv
包可以方便地在字符串和其他类型之间进行转换。 - 布尔类型:布尔值和字符串之间的转换需要手动处理,使用
strconv.ParseBool
可以将字符串转换为布尔值,使用fmt.Sprintf
可以将布尔值转换为字符串。
控制结构
条件语句
package mainimport "fmt"func main() {age := 18if age >= 18 {fmt.Println("You are an adult.")} else {fmt.Println("You are a minor.")}// switch语句day := 2switch day {case 1:fmt.Println("Monday")case 2:fmt.Println("Tuesday")default:fmt.Println("Other day")}
}
循环语句
Go语言中只有一种循环结构:for
。
package mainimport "fmt"func main() {// 基本的for循环for i := 0; i < 5; i++ {fmt.Println(i)}// 类似while循环count := 0for 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)}
}
range 关键字主要用于遍历集合类型的数据结构,如数组、切片、映射(map)、字符串等。range 可以方便地获取集合中的每个元素及其索引或键值。
4. 函数和多返回值
package mainimport "fmt"// 定义一个有返回值的函数
func add(x int, y int) int {return x + y
}// 多返回值函数
func divide(dividend, divisor int) (int, int) {quotient := dividend / divisorremainder := dividend % divisorreturn 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 mainimport "fmt"func main() {x := 10ptr := &x // 获取变量的地址fmt.Println("Address of x:", ptr)fmt.Println("Value of x:", *ptr) // 通过指针访问变量值
}
6. 结构体与方法
package mainimport "fmt"// 定义一个结构体
type Person struct {Name stringAge 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 mainimport "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 mainimport ("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") // 启动goroutineprintMessage("Hello from main")// 使用通道ch := make(chan string)go func() {ch <- "Data sent to channel"}()msg := <-ch // 从通道接收数据fmt.Println("Received:", msg)
}
9. 错误处理
package mainimport ("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的标准库及第三方库来实现更多功能。