Skip to content

golang基础

变量类型

下面是Golang中变量类型的分类和示例,整理成表格形式:

基本类型

类型示例代码说明
布尔类型var b bool = true布尔值,取值为truefalse
整数类型var i int = 10有符号整数
无符号整数var u uint = 20无符号整数
int8var i8 int8 = -1288位有符号整数
uint8var ui8 uint8 = 2558位无符号整数
int16var i16 int16 = 3276716位有符号整数
uint16var ui16 uint16 = 6553516位无符号整数
int32var i32 int32 = 214748364732位有符号整数
uint32var ui32 uint32 = 429496729532位无符号整数
int64var i64 int64 = 922337203685477580764位有符号整数
uint64var ui64 uint64 = 1844674407370955161564位无符号整数
浮点数类型var f32 float32 = 3.1432位浮点数
浮点数类型var f64 float64 = 2.71864位浮点数
复数类型var c64 complex64 = 1 + 2i64位复数
复数类型var c128 complex128 = 3 + 4i128位复数
字符串类型var s string = "Hello, Go"字符串

复合类型

类型示例代码说明
数组var arr [5]int = [5]int{1, 2, 3, 4, 5}固定长度的相同类型元素的集合
切片var slice []int = []int{1, 2, 3, 4, 5}动态数组
字典 (Map)var m map[string]int = map[string]int{"one": 1, "two": 2}键值对的集合
结构体type Person struct { Name string; Age int }自定义类型的集合
指针var ptr *int; var x int = 10; ptr = &x指向某个变量地址的变量
函数var f func(int) int; f = func(x int) int { return x * x }函数类型
接口type Stringer interface { String() string }定义一组方法的集合

常量类型

类型示例代码说明
常量const pi float64 = 3.1415定义后值不可更改
批量常量声明const (Sunday = 0; Monday = 1; Tuesday = 2)批量声明多个常量

示例代码

package main
import (
"fmt"
)
func main() {
// 基本类型
var b bool = true
var i int = 42
var u uint = 100
var f32 float32 = 3.14
var f64 float64 = 2.718
var c64 complex64 = 1 + 2i
var s string = "Hello, Go"
// 复合类型
var arr [3]int = [3]int{1, 2, 3}
var slice []int = []int{4, 5, 6}
var m map[string]int = map[string]int{"one": 1, "two": 2}
type Person struct {
Name string
Age int
}
var p Person = Person{Name: "Alice", Age: 25}
var ptr *int
var x int = 10
ptr = &x
var f func(int) int
f = func(x int) int {
return x * x
}
// 打印变量
fmt.Println(b)
fmt.Println(i, u)
fmt.Println(f32, f64)
fmt.Println(c64)
fmt.Println(s)
fmt.Println(arr)
fmt.Println(slice)
fmt.Println(m)
fmt.Println(p)
fmt.Println(*ptr)
fmt.Println(f(5))
}

类型转换

在Go语言(Golang)中,类型转换(Type Conversion)用于将一种数据类型的值转换为另一种数据类型的值。Go语言不支持隐式类型转换,所有的类型转换都必须显式指定。以下是一些常见的类型转换方式和示例。

基本数据类型的转换

在Go中,可以使用类型名作为函数来进行类型转换。例如:

package main
import (
"fmt"
)
func main() {
var a int = 10
var b float64 = float64(a) // int 转换为 float64
var c int = int(b) // float64 转换为 int
fmt.Printf("a: %d, b: %f, c: %d\n", a, b, c)
var d string = "123"
var e int
fmt.Sscanf(d, "%d", &e) // string 转换为 int
fmt.Printf("d: %s, e: %d\n", d, e)
}

字符串和数值类型之间的转换

在Go中,字符串和数值类型之间的转换可以通过标准库中的 strconv 包实现。

package main
import (
"fmt"
"strconv"
)
func main() {
// int 转换为 string
var a int = 10
var strA string = strconv.Itoa(a)
fmt.Printf("strA: %s\n", strA)
// string 转换为 int
var strB string = "20"
var b int
var err error
b, err = strconv.Atoi(strB)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("b: %d\n", b)
}
// float64 转换为 string
var c float64 = 3.14159
var strC string = strconv.FormatFloat(c, 'f', -1, 64)
fmt.Printf("strC: %s\n", strC)
// string 转换为 float64
var strD string = "2.71828"
var d float64
d, err = strconv.ParseFloat(strD, 64)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Printf("d: %f\n", d)
}
}

自定义类型的转换

Go语言允许定义自定义类型,并且可以通过显式转换在这些类型之间进行转换。

package main
import (
"fmt"
)
type MyInt int
func main() {
var a int = 10
var b MyInt = MyInt(a) // int 转换为 MyInt
fmt.Printf("b: %d\n", b)
var c MyInt = 20
var d int = int(c) // MyInt 转换为 int
fmt.Printf("d: %d\n", d)
}

接口类型的转换

在Go中,接口类型的转换可以通过类型断言(type assertion)和类型开关(type switch)实现。

类型断言

类型断言用于将接口类型转换为具体类型。

package main
import (
"fmt"
)
func main() {
var i interface{} = "hello"
// 类型断言
s, ok := i.(string)
if ok {
fmt.Printf("s: %s\n", s)
} else {
fmt.Println("i is not a string")
}
}

类型开关

类型开关用于根据接口变量的具体类型执行不同的操作。

package main
import (
"fmt"
)
func main() {
var i interface{} = 10
switch v := i.(type) {
case int:
fmt.Printf("i is an int: %d\n", v)
case float64:
fmt.Printf("i is a float64: %f\n", v)
case string:
fmt.Printf("i is a string: %s\n", v)
default:
fmt.Println("unknown type")
}
}

字符串格式化

在Go语言中,字符串格式化有几种常见的方法,以下是每种方法的示例:

1. 使用 fmt.Sprintf

这是Go语言中最常用的字符串格式化函数。

package main
import (
"fmt"
)
func main() {
name := "Alice"
age := 30
formattedString := fmt.Sprintf("My name is %s and I am %d years old.", name, age)
fmt.Println(formattedString)
}

2. 使用 fmt.Printf

这种方法适用于直接输出格式化的字符串。

package main
import (
"fmt"
)
func main() {
name := "Alice"
age := 30
fmt.Printf("My name is %s and I am %d years old.\n", name, age)
}

3. 使用 fmt.Fprintf

这种方法适用于将格式化字符串写入到一个 io.Writer 接口(例如文件或网络连接)。

package main
import (
"fmt"
"os"
)
func main() {
name := "Alice"
age := 30
fmt.Fprintf(os.Stdout, "My name is %s and I am %d years old.\n", name, age)
}

4. 使用 strings.Builder

对于大量的字符串拼接和构建,strings.Builder 提供了更高效的方法。

package main
import (
"fmt"
"strings"
)
func main() {
name := "Alice"
age := 30
var sb strings.Builder
sb.WriteString("My name is ")
sb.WriteString(name)
sb.WriteString(" and I am ")
fmt.Fprintf(&sb, "%d", age)
sb.WriteString(" years old.")
formattedString := sb.String()
fmt.Println(formattedString)
}

5. 使用字符串拼接

对于简单的字符串拼接,可以直接使用 + 运算符。

package main
import (
"fmt"
)
func main() {
name := "Alice"
age := 30
formattedString := "My name is " + name + " and I am " + fmt.Sprint(age) + " years old."
fmt.Println(formattedString)
}

变量定义

在Golang中,变量的定义有多种方式,主要包括显式类型声明、隐式类型声明、批量声明和短变量声明。以下是详细介绍:

1. 显式类型声明

这是最基本的变量定义方式,明确指定变量的类型。

var x int
x = 10
var y string
y = "Hello"

2. 隐式类型声明

Go支持根据初始值自动推断变量类型,因此可以省略类型声明。

var a = 10 // a 是 int 类型
var b = "Go" // b 是 string 类型

3. 短变量声明

这是Go特有的一种简洁的变量声明方式,常用于函数内部。这种方式只能用于函数内部,不能用于全局变量。

c := 20 // c 是 int 类型
d := "Go" // d 是 string 类型

4. 批量声明

可以在一个声明语句中定义多个变量。

var (
e int
f string
g float64
)

或者使用批量声明并赋值:

var (
h, i int = 5, 10
j, k = "Hello", 15.5
)

5. 常量声明

常量的值在程序运行时不能改变。常量声明可以显式指定类型,也可以使用隐式类型。

const pi float64 = 3.1415
const e = 2.7182

批量声明常量:

const (
Sunday = 0
Monday = 1
Tuesday = 2
)

6. 全局变量

全局变量可以在包级别声明,通常在包的最上方。

package main
import "fmt"
var globalVar int = 100
func main() {
fmt.Println(globalVar)
}

示例代码

以下是一个示例代码,展示了上述各种变量声明方式的使用:

package main
import "fmt"
// 全局变量
var global int = 100
func main() {
// 显式类型声明
var x int
x = 10
// 隐式类型声明
var y = "Hello"
// 短变量声明
z := 3.14
// 批量声明
var (
a int
b string
)
a = 20
b = "Go"
// 常量声明
const pi float64 = 3.1415
// 打印变量
fmt.Println(x)
fmt.Println(y)
fmt.Println(z)
fmt.Println(a, b)
fmt.Println(pi)
fmt.Println(global)
}

条件判断

在Golang中,判断语句主要包括if语句和switch语句。下面是这些语句的详细介绍和示例。

if 语句

if 语句用于根据条件执行代码块。

基本语法

if condition {
// 执行的代码
}

带有elseif语句

if condition {
// 条件为 true 时执行的代码
} else {
// 条件为 false 时执行的代码
}

多个条件判断

if condition1 {
// 条件1为 true 时执行的代码
} else if condition2 {
// 条件2为 true 时执行的代码
} else {
// 上述条件都为 false 时执行的代码
}

示例代码

package main
import (
"fmt"
)
func main() {
x := 10
if x > 5 {
fmt.Println("x is greater than 5")
} else if x == 5 {
fmt.Println("x is equal to 5")
} else {
fmt.Println("x is less than 5")
}
}

switch 语句

switch 语句用于基于多个条件值执行不同的代码块。

基本语法

switch variable {
case value1:
// 当变量等于 value1 时执行的代码
case value2:
// 当变量等于 value2 时执行的代码
default:
// 变量不等于任何已定义的值时执行的代码
}

switch 语句中的表达式

switch 语句也可以基于表达式的结果进行判断。

switch {
case x > 10:
fmt.Println("x is greater than 10")
case x == 10:
fmt.Println("x is equal to 10")
default:
fmt.Println("x is less than 10")
}

示例代码

package main
import (
"fmt"
)
func main() {
day := "Monday"
switch day {
case "Monday":
fmt.Println("Today is Monday")
case "Tuesday":
fmt.Println("Today is Tuesday")
default:
fmt.Println("Today is not Monday or Tuesday")
}
x := 10
switch {
case x > 10:
fmt.Println("x is greater than 10")
case x == 10:
fmt.Println("x is equal to 10")
default:
fmt.Println("x is less than 10")
}
}

循环

在Golang中,循环语句主要包括for循环。Go语言只有一种循环结构,即for循环,但它可以通过不同的方式使用来实现多种循环功能,例如普通的for循环、基于条件的循环、和无限循环。下面是这些循环语句的详细介绍和示例。

基本的 for 循环

语法

for 初始化语句; 条件表达式; 迭代语句 {
// 循环体
}

示例

package main
import (
"fmt"
)
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}

基于条件的 for 循环

语法

for 条件表达式 {
// 循环体
}

示例

package main
import (
"fmt"
)
func main() {
i := 0
for i < 5 {
fmt.Println(i)
i++
}
}

无限循环

语法

for {
// 循环体
}

示例

package main
import (
"fmt"
)
func main() {
i := 0
for {
fmt.Println(i)
i++
if i >= 5 {
break
}
}
}

for 循环遍历数组、切片、map 和字符串

数组和切片

package main
import (
"fmt"
)
func main() {
arr := []int{1, 2, 3, 4, 5}
for index, value := range arr {
fmt.Println(index, value)
}
}

Map

package main
import (
"fmt"
)
func main() {
m := map[string]int{"a": 1, "b": 2, "c": 3}
for key, value := range m {
fmt.Println(key, value)
}
}

字符串

package main
import (
"fmt"
)
func main() {
str := "Hello"
for index, runeValue := range str {
fmt.Printf("%d: %c\n", index, runeValue)
}
}

函数

在Golang中,函数是代码组织和重用的基本单位。函数定义包括函数名、参数列表、返回值类型以及函数体。下面是Go中函数定义的详细介绍和示例。

基本函数定义

语法

func 函数名(参数列表) 返回值类型 {
// 函数体
}

示例

package main
import (
"fmt"
)
func add(x int, y int) int {
return x + y
}
func main() {
sum := add(3, 4)
fmt.Println(sum) // 输出: 7
}

多返回值函数

Go函数可以返回多个值,这在处理错误时非常有用。

示例

package main
import (
"fmt"
"errors"
)
func divide(x, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result) // 输出: 5
}
}

命名返回值

Go允许为返回值命名,这样可以在函数体内直接使用这些变量,并且可以使用return关键字返回所有命名返回值。

示例

package main
import (
"fmt"
)
func swap(x, y string) (first, second string) {
first = y
second = x
return
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b) // 输出: world hello
}

匿名函数和闭包

匿名函数是在没有名字的情况下定义的函数,闭包是引用了其外围作用域中变量的函数。

示例

package main
import (
"fmt"
)
func main() {
add := func(x, y int) int {
return x + y
}
fmt.Println(add(3, 4)) // 输出: 7
// 闭包示例
counter := func() func() int {
i := 0
return func() int {
i++
return i
}
}
c := counter()
fmt.Println(c()) // 输出: 1
fmt.Println(c()) // 输出: 2
}

变参函数

变参函数可以接受不定数量的参数。

示例

package main
import (
"fmt"
)
func sum(numbers ...int) int {
total := 0
for _, number := range numbers {
total += number
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3, 4)) // 输出: 10
fmt.Println(sum(10, 20)) // 输出: 30
}

方法

方法是绑定到特定类型的函数。

示例

package main
import (
"fmt"
)
type Rectangle struct {
width, height float64
}
// 为Rectangle类型定义方法
func (r Rectangle) area() float64 {
return r.width * r.height
}
func main() {
rect := Rectangle{width: 10, height: 5}
fmt.Println("Area:", rect.area()) // 输出: Area: 50
}

组合

在Golang中,继承不是通过类和子类来实现的,因为Go没有传统的类继承机制。相反,Go通过组合和接口来实现代码复用和多态性。

组合

组合是一种包含一个或多个其他结构体类型的字段,从而实现类似于继承的效果。

示例

package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
type Employee struct {
Person
Position string
}
func main() {
emp := Employee{
Person: Person{
Name: "Alice",
Age: 30,
},
Position: "Engineer",
}
// 可以直接访问嵌入的Person结构体的字段
fmt.Println("Name:", emp.Name)
fmt.Println("Age:", emp.Age)
fmt.Println("Position:", emp.Position)
}

在这个示例中,Employee结构体嵌入了Person结构体,从而可以直接访问Person的字段,就像传统的继承一样。

接口

接口定义了一组方法,任何实现了这些方法的类型都被认为实现了该接口。接口用于实现多态性。

示例

package main
import (
"fmt"
)
// 定义一个接口
type Describer interface {
Describe() string
}
type Person struct {
Name string
Age int
}
func (p Person) Describe() string {
return fmt.Sprintf("%s is %d years old.", p.Name, p.Age)
}
type Employee struct {
Person
Position string
}
func (e Employee) Describe() string {
return fmt.Sprintf("%s is %d years old and works as a %s.", e.Name, e.Age, e.Position)
}
func main() {
var d Describer
p := Person{Name: "Bob", Age: 24}
d = p
fmt.Println(d.Describe())
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Position: "Engineer",
}
d = e
fmt.Println(d.Describe())
}

在这个示例中,PersonEmployee都实现了Describer接口的Describe方法,因此它们可以作为Describer类型使用。

多态性

通过组合和接口,Go实现了多态性,可以根据实际类型调用相应的方法。

示例

package main
import (
"fmt"
)
type Describer interface {
Describe() string
}
type Person struct {
Name string
Age int
}
func (p Person) Describe() string {
return fmt.Sprintf("%s is %d years old.", p.Name, p.Age)
}
type Employee struct {
Person
Position string
}
func (e Employee) Describe() string {
return fmt.Sprintf("%s is %d years old and works as a %s.", e.Name, e.Age, e.Position)
}
func printDescription(d Describer) {
fmt.Println(d.Describe())
}
func main() {
p := Person{Name: "Bob", Age: 24}
e := Employee{
Person: Person{Name: "Alice", Age: 30},
Position: "Engineer",
}
printDescription(p)
printDescription(e)
}

在这个示例中,printDescription函数接受一个Describer接口类型的参数,因此它可以处理任何实现了Describer接口的类型。

错误处理

在Golang中,错误处理是一种显式的、结构化的方式,主要通过返回错误值来处理。Go语言没有异常机制,而是通过返回值的方式处理错误。以下是Go语言中错误处理的详细介绍和示例。

错误类型

Go语言中,错误类型是内置的接口类型error,它定义如下:

type error interface {
Error() string
}

任何实现了Error()方法的类型都可以作为错误类型。

创建错误

可以使用errors包中的New函数创建一个简单的错误:

import (
"errors"
)
var errExample = errors.New("this is an example error")

返回错误

函数通常会返回一个error类型的值来表示是否发生了错误。返回值可以是nil,表示没有错误。

示例

package main
import (
"errors"
"fmt"
)
// 定义一个可能返回错误的函数
func divide(x, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("division by zero")
}
return x / y, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result, err = divide(10, 0)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}

自定义错误类型

可以定义一个自定义的错误类型,通过实现Error()方法来满足error接口。

示例

package main
import (
"fmt"
)
// 自定义错误类型
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("Error %d: %s", e.Code, e.Message)
}
func riskyOperation(x int) (int, error) {
if x < 0 {
return 0, &MyError{Code: 123, Message: "negative number"}
}
return x * 2, nil
}
func main() {
result, err := riskyOperation(-1)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
result, err = riskyOperation(10)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Result:", result)
}
}

包装错误

可以使用fmt.Errorf来格式化和包装错误信息。

示例

package main
import (
"fmt"
"errors"
)
func validateAge(age int) error {
if age < 0 {
return fmt.Errorf("invalid age %d: %w", age, errors.New("age cannot be negative"))
}
return nil
}
func main() {
err := validateAge(-5)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Age is valid")
}
}

通过errors包检查和比较错误

Go 1.13引入了errors.Iserrors.As函数,用于错误检查和比较。

示例

package main
import (
"errors"
"fmt"
)
var ErrNegativeNumber = errors.New("negative number")
func riskyOperation(x int) (int, error) {
if x < 0 {
return 0, ErrNegativeNumber
}
return x * 2, nil
}
func main() {
_, err := riskyOperation(-1)
if err != nil {
if errors.Is(err, ErrNegativeNumber) {
fmt.Println("Encountered a negative number error")
} else {
fmt.Println("Encountered an unknown error")
}
}
}

项目结构

在Golang中,项目结构的设计是开发过程中一个非常重要的部分。一个良好的项目结构可以提高代码的可维护性和可扩展性。以下是一个典型的Go项目结构以及每个部分的说明和示例。

典型的Go项目结构

myproject/
├── bin/
├── cmd/
│ ├── myapp/
│ │ └── main.go
├── pkg/
│ └── mypackage/
│ ├── mypackage.go
│ └── mypackage_test.go
├── internal/
│ └── myinternalpackage/
│ ├── myinternalpackage.go
│ └── myinternalpackage_test.go
├── api/
│ └── v1/
│ ├── api.go
│ └── api_test.go
├── web/
│ ├── static/
│ └── templates/
├── scripts/
│ └── build.sh
├── configs/
│ └── config.yaml
├── deployments/
│ └── deployment.yaml
├── test/
│ ├── e2e/
│ └── integration/
├── go.mod
└── go.sum

目录和文件说明

  • bin/:编译后的二进制文件目录。可以通过构建脚本将编译输出设置到此目录。

  • cmd/:存放项目的主要应用入口。每个子目录代表一个可独立构建和运行的应用。

    • cmd/myapp/main.go:项目的主要入口文件,包含 main 函数。
  • pkg/:存放可供其他项目使用的库代码。

    • pkg/mypackage/:一个公共包,包含主要功能代码和测试文件。
  • internal/:存放私有包,这些包仅对本项目可见,其他项目无法导入。

    • internal/myinternalpackage/:一个内部包,包含内部功能代码和测试文件。
  • api/:存放API相关代码,通常用于定义API路由和处理请求。

    • api/v1/:API的v1版本目录。
    • api/v1/api.go:API的实现文件。
    • api/v1/api_test.go:API的测试文件。
  • web/:存放Web相关资源,包括静态文件和模板。

    • web/static/:静态文件目录。
    • web/templates/:模板文件目录。
  • scripts/:存放构建、安装、分析等操作的脚本。

    • scripts/build.sh:构建脚本。
  • configs/:存放配置文件。

    • configs/config.yaml:配置文件示例。
  • deployments/:存放部署相关文件。

    • deployments/deployment.yaml:Kubernetes部署文件示例。
  • test/:存放测试相关文件。

    • test/e2e/:端到端测试文件目录。
    • test/integration/:集成测试文件目录。
  • go.mod:模块依赖管理文件,定义项目的模块名称和依赖项。

  • go.sum:记录所有依赖项的精确版本和校验和。

示例代码

cmd/myapp/main.go

package main
import (
"example.com/myproject/pkg/mypackage"
"fmt"
)
func main() {
result := mypackage.MyFunction()
fmt.Println(result)
}

pkg/mypackage/mypackage.go

package mypackage
func MyFunction() string {
return "Hello from mypackage!"
}

pkg/mypackage/mypackage_test.go

package mypackage
import "testing"
func TestMyFunction(t *testing.T) {
expected := "Hello from mypackage!"
result := MyFunction()
if result != expected {
t.Errorf("expected '%s' but got '%s'", expected, result)
}
}

其他常见目录结构

根据项目的规模和需求,项目结构可以有所不同。以下是一些常见的项目结构变化:

  • 小型项目:不需要复杂的目录结构,可以直接在根目录下放置所有代码。

    myproject/
    ├── main.go
    ├── mypackage.go
    ├── mypackage_test.go
    ├── go.mod
    └── go.sum
  • 大型项目:可以引入更多的目录层次和模块划分。

    myproject/
    ├── cmd/
    ├── pkg/
    ├── internal/
    ├── api/
    ├── web/
    ├── scripts/
    ├── configs/
    ├── deployments/
    ├── test/
    ├── docs/ # 文档目录
    ├── tools/ # 工具目录
    ├── third_party/# 第三方库目录
    ├── examples/ # 示例代码目录
    ├── go.mod
    └── go.sum