这是我整理非常全的go语言基础知识点以及代码实例,对GO有情趣的同学可以通过这个总结以及代码实例快速入门!加油同学们!
是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
相比java,go没有jvm进行代码编译,直接把代码转换为二进制代码,执行效率更高。为每个平台单独编译二进制文件,也支持跨平台。
// 行注释 快捷键 ctrl+/ 官方推荐 行注释
/* 要注释的内容 */ 块注释
正确的注释和注释风格:
Go官方推荐使用行注释来注释整个方法和语句。 带看Go源码
正确的缩进和空白
使用一次tab操作,实现缩进,默认整体向右边移动,时候用shift+tab整体向左移
运算符两边习惯性各加一个空格。比如: 2 + 4*5。
即创建GOPATH目录,并在GOPATH下建立三个目录:
bin:存放编译后生成的可执行文件(.exe)
pkg:存放编译后生成的包文件(.a)
src:存放项目源码(.go)
第一个简单的go程序,hello.go
package main
import "fmt"
func main() {
fmt.Println("hello world!")
// fmt.Println("hello world!")
// fmt.Println("hello world!")
for i := 1; i < 10; i++ {
for j := 1; j <= i; j++ {
fmt.Printf("%d * %d = %d\t", j, i, j*i)
}
fmt.Println()
}
}
Go语言中的常量是在程序运行时不可更改的值。常量可以是数字、字符(rune)、字符串或布尔类型。常量的声明使用关键字const
。
常量的语法如下:
const identifier [type] = value
其中,identifier
是常量的名称,type
是常量的类型(可省略),value
是常量的值。
常量的特点:
下面是一些常量的示例代码:
package main
import "fmt"
func main() {
// 声明整型常量
const a int = 10
fmt.Println(a) // 输出:10
// 声明浮点型常量
const b float64 = 3.14
fmt.Println(b) // 输出:3.14
// 声明字符串常量
const c string = "Hello, World!"
fmt.Println(c) // 输出:"Hello, World!"
// 声明布尔型常量
const d bool = true
fmt.Println(d) // 输出:true
// 使用表达式初始化常量
const e = 2 * 3.14
fmt.Println(e) // 输出:6.28
}
在Go语言中,常量的命名规则与变量相同,使用驼峰命名法。另外,常量也支持枚举类型的定义,可以一次声明多个常量,并按顺序赋予不同的值。
驼峰命名法(Camel Case)是一种命名规则,常用于变量、函数、常量等标识符的命名。它的命名规则是将多个单词连接在一起,每个单词的首字母大写(除了第一个单词)。这种命名方式的形状和骆驼的驼峰背部形状相似,因此得名。
驼峰命名法有两种常见的风格:小驼峰命名法(lower camel case)和大驼峰命名法(upper camel case)。
myVariableName
, myFunctionName
, myConstantValue
MyStructName
, MyPackageName
, MyEnumValue
使用驼峰命名法的好处是可以使代码更易读、更具可读性,并且符合了Go语言的命名惯例。
在Go中,变量用于存储数据,可以是基本类型(比如整型、字符串、布尔值)或者复合类型(比如数组、切片、结构体)。下面我将详细介绍Go语言中的变量,并提供一些代码示例。
var
,后跟变量名称和类型。:=
可以进行变量的自动类型推导和初始化。示例代码:
var a int // 声明一个整型变量,初始值为0
var b string // 声明一个字符串变量,初始值为空字符串
c := 10 // 自动推导为整型变量,并赋值为10
d, e := "Hello", 3.14 // 自动推导为字符串和浮点型变量,并分别赋值
=
来给其赋值。示例代码:
var a int
a = 10 // 将10赋值给变量a
var b float64
b = float64(a) // 将变量a的值转换为float64类型并赋给变量b
var c int
c = int(b) // 将变量b的值转换为int类型并赋给变量c
示例代码:
var globalVariable int // 全局变量
func main() {
localVariable := 10 // 局部变量,只能在main函数中使用
fmt.Println(localVariable) // 输出:10
fmt.Println(globalVariable) // 输出:0
shadowVariable := 20 // 声明一个与全局变量同名的局部变量
fmt.Println(shadowVariable) // 输出:20,优先使用局部变量
}
Go语言的命名规则如下:
package main
import "fmt"
const MAX_SIZE = 10
type MyTypeName struct {
myField int
}
func myFunction(myVariableName int) {
fmt.Println(myVariableName)
}
func main() {
myVar := 5
myFunction(myVar)
mt := MyTypeName{myField: 10}
fmt.Println(mt.myField)
}
在上述代码示例中,我们遵守了Go语言的命名规则,变量名使用驼峰式命名法(myVariableName),常量名使用全大写并使用下划线分隔(MAX_SIZE),类型名使用驼峰式命名法(MyTypeName),包名使用全小写(main),函数名使用驼峰式命名法(myFunction)。
Go语言的数据类型分为基本类型和复合类型。
代码示例:
package main
import "fmt"
func main() {
// 声明变量并赋初值
var num1 int = 10
var num2 float32 = 3.14
var flag bool = true
var str string = "Hello, Go!"
// 输出变量的值和类型
fmt.Printf("num1: %d, type: %T\n", num1, num1)
fmt.Printf("num2: %.2f, type: %T\n", num2, num2)
fmt.Printf("flag: %t, type: %T\n", flag, flag)
fmt.Printf("str: %s, type: %T\n", str, str)
// 数组示例
var arr [3]int
arr[0] = 1
arr[1] = 2
arr[2] = 3
fmt.Println("arr:", arr)
// 切片示例
slice := []int{1, 2, 3, 4, 5}
fmt.Println("slice:", slice)
// 映射示例
m := make(map[string]int)
m["a"] = 1
m["b"] = 2
fmt.Println("map:", m)
// 结构体示例
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 20}
fmt.Println("person:", p)
// 指针示例
ptr := &num1
fmt.Println("pointer:", ptr)
// 函数示例
add := func(a, b int) int {
return a + b
}
result := add(3, 4)
fmt.Println("result:", result)
}
值类型(Value Type):包括基本数据类型(如整型、浮点型、布尔型等)以及数组和结构体。值类型在赋值或者作为参数传递时,会进行值拷贝,即会创建一个新的副本。
引用类型(Reference Type):包括切片、映射、通道和接口。引用类型的变量存储的是数据的地址,而不是数据本身。在赋值或者传递参数时,只是复制了一个指向同一块数据的指针。
下面是一个简单的代码实例来说明值类型和引用类型的区别:
package main
import "fmt"
func modifyValue(val int) {
val = 100
}
func modifySlice(slice []int) {
slice[0] = 100
}
func main() {
// 值类型示例
num := 10
fmt.Println("Before modifyValue:", num)
modifyValue(num)
fmt.Println("After modifyValue:", num)
// 引用类型示例
numbers := []int{1, 2, 3}
fmt.Println("Before modifySlice:", numbers)
modifySlice(numbers)
fmt.Println("After modifySlice:", numbers)
}
输出结果:
Before modifyValue: 10
After modifyValue: 10
Before modifySlice: [1 2 3]
After modifySlice: [100 2 3]
在上述代码中,modifyValue
函数接收一个值类型的参数val
,并尝试修改它的值。但在main
函数中调用modifyValue
后,原始变量num
的值并未改变。
而modifySlice
函数接收一个引用类型的参数slice
,并修改其第一个元素的值。在main
函数中调用modifySlice
后,原始变量numbers
的对应元素值被修改。
这是因为值类型在赋值或者传递时会发生拷贝,而引用类型则是共享同一块数据内存地址。
Go语言的字符串类型是以字节序列形式存储的不可变的Unicode字符集。字符串类型使用双引号(“”)或反引号(``)括起来。
在Go语言中,字符串类型是值类型,即在赋值或传递参数时会进行拷贝。但是,由于字符串是不可变的,实际上拷贝的是指向底层数据的指针和长度信息,而不会重新分配新的内存空间。
下面是一个简单的代码示例来说明字符串类型的特点:
package main
import "fmt"
func main() {
str1 := "Hello, World!" // 使用双引号定义字符串
str2 := `I'm a string` // 使用反引号定义字符串
// 访问字符串中的单个字符
fmt.Println("str1[0]:", str1[0]) // 输出:72 (Unicode码对应的ASCII值)
fmt.Println("str2[2]:", str2[2]) // 输出:m (Unicode码对应的字符)
// 字符串拼接
str3 := "Hello, " + "Go!"
fmt.Println("str3:", str3) // 输出:Hello, Go!
// 字符串长度
fmt.Println("Length of str1:", len(str1)) // 输出:13
fmt.Println("Length of str2:", len(str2)) // 输出:12
// 字符串切片
slice := str1[7:12]
fmt.Println("Slice of str1:", slice) // 输出:World
// 修改字符串(不可行,字符串是不可变的)
// str1[0] = 'h' // 编译错误
// 转换为字节数组
bytes := []byte(str1)
bytes[0] = 'h'
str4 := string(bytes)
fmt.Println("str4:", str4) // 输出:hello, World!
}
在上述代码中,我们通过len
函数可以获取字符串的长度。同时,使用索引访问字符串中的单个字符,注意每个字符对应的是Unicode码。此外,可以使用切片操作获取子串,但是无法修改原始字符串的内容。
如果需要修改字符串,可以将其转换为字节数组,进行修改后再转换回字符串形式。这是因为字节数组是可变的。
需要注意的是,每个ASCII字符用一个字节表示,而非ASCII字符则可能占用多个字节。因此,在处理含有非ASCII字符的字符串时,需要特别注意字节和字符之间的转换以及长度计算。
Go语言中的数组是一种固定长度、具有相同类型的数据结构。数组的长度是在定义时就确定的,且不能更改。
定义数组的语法如下:
var 变量名 [长度]类型
其中,变量名是数组的标识符,长度是一个常量表达式,类型是数组中元素的类型。例如,定义一个包含5个整数的数组:
var numbers [5]int
可以直接给数组赋初值:
var numbers = [5]int{1, 2, 3, 4, 5}
也可以使用索引来给数组元素赋值:
numbers[0] = 1
numbers[1] = 2
// ...
访问数组元素的方式也是使用索引:
value := numbers[0]
需要注意的是,数组的索引从0开始,最大索引为长度减1。
数组的长度可以使用内置函数len()
获取:
length := len(numbers)
在Go语言中,数组是值类型,当数组被传递给函数时,会进行值的复制。如果想要在函数中修改数组的值,可以使用指针或切片。
下面是一个示例代码,展示了数组的基本操作:
package main
import "fmt"
func main() {
var numbers = [5]int{1, 2, 3, 4, 5}
// 访问数组元素
value := numbers[0]
fmt.Println("第一个元素:", value)
// 修改数组元素
numbers[0] = 10
fmt.Println("修改后的数组:", numbers)
// 数组长度
length := len(numbers)
fmt.Println("数组长度:", length)
}
Go语言中的切片(slice)是一种动态数组,与固定长度的数组相比,切片具有更灵活的长度。切片是基于数组的一种封装,可以按需增加或缩减其长度。
定义切片的语法如下:
var 变量名 []类型
其中,变量名是切片的标识符,类型是切片中元素的类型。例如,定义一个整数切片:
var numbers []int
切片可以使用make()
函数来创建,并指定切片的长度和容量:
numbers := make([]int, 5, 10)
上述代码创建了一个长度为5,容量为10的整数切片。长度表示切片当前存储的元素个数,容量表示底层数组的大小。
切片可以直接通过索引来访问和修改元素,使用与数组相同的语法:
numbers[0] = 1 // 修改第一个元素
value := numbers[0] // 访问第一个元素
切片的长度可以使用内置函数len()
获取:
length := len(numbers)
切片还提供了一些操作函数和方法,例如追加元素、拷贝切片、截取切片等。以下是一些常用的操作:
追加元素:
numbers = append(numbers, 6, 7) // 追加一个或多个元素
拷贝切片:
newSlice := make([]int, len(numbers))
copy(newSlice, numbers) // 将numbers拷贝到newSlice
截取切片:
sliced := numbers[1:3] // 获取索引1到3的子切片(不包含索引3)
下面是一个示例代码,展示了切片的基本操作:
package main
import "fmt"
func main() {
numbers := make([]int, 0, 5)
fmt.Println("初始切片:", numbers)
// 追加元素
numbers = append(numbers, 1, 2, 3, 4, 5)
fmt.Println("追加元素后的切片:", numbers)
// 修改元素
numbers[0] = 10
fmt.Println("修改后的切片:", numbers)
// 切片长度和容量
length := len(numbers)
capacity := cap(numbers)
fmt.Println("切片长度:", length)
fmt.Println("切片容量:", capacity)
// 拷贝切片
newSlice := make([]int, length)
copy(newSlice, numbers)
fmt.Println("拷贝的切片:", newSlice)
// 截取切片
sliced := numbers[1:3]
fmt.Println("截取的切片:", sliced)
}
字典(Dictionary)是Go语言中一种无序的数据结构,它由键值对(key-value)组成。字典中的每个键(key)都是唯一的,而值(value)可以是任意类型的数据。
在Go语言中,字典的声明形式如下:
var 字典名 map[键类型]值类型
其中,键类型和值类型可以是任意合法的数据类型,例如string、int、bool等。
字典的初始化可以通过make函数或者字面量的方式来完成。使用make函数初始化字典时,需要指定字典的键类型和值类型,并且分配内存空间,例如:
字典名 := make(map[键类型]值类型)
使用字面量初始化字典时,可以直接在大括号中定义键值对,例如:
字典名 := map[键类型]值类型{
键1: 值1,
键2: 值2,
...
}
字典的基本操作包括添加元素、删除元素、检查元素是否存在以及获取元素的值。
添加元素可以通过键值对的方式实现,例如:
字典名[键] = 值
删除元素可以使用delete函数,例如:
delete(字典名, 键)
检查元素是否存在可以使用以下方式:
值, 存在 := 字典名[键]
获取元素的值可以直接通过键访问,例如:
值 := 字典名[键]
下面是一个使用字典的代码实例:
package main
import "fmt"
func main() {
// 初始化字典
scores := make(map[string]int)
// 添加元素
scores["Alice"] = 100
scores["Bob"] = 90
scores["Charlie"] = 95
// 获取元素的值
fmt.Println(scores["Alice"])
// 检查元素是否存在
score, exists := scores["Bob"]
if exists {
fmt.Println("Bob's score:", score)
}
// 删除元素
delete(scores, "Charlie")
// 遍历字典
for name, score := range scores {
fmt.Println(name, ":", score)
}
}
通道(channel)是Go语言中一种用于在多个goroutine之间进行通信和数据同步的机制。它可以想象成一个队列,goroutine可以通过发送和接收操作来向通道发送和接收数据。
通道类型使用chan
关键字进行声明,其语法为:
var 变量名 chan 数据类型
其中,数据类型
表示通道中传输的数据类型。
通道的创建使用make()
函数来完成,语法如下:
变量名 := make(chan 数据类型)
通道有两种类型:带缓冲的通道和非缓冲的通道。
带缓冲的通道可以在没有接收方的情况下,缓存一定数量的元素。当通道的缓存已满时,发送操作将被阻塞,直到有接收方接收元素并释放空间。
变量名 := make(chan 数据类型, 缓冲大小)
非缓冲的通道又被称为同步通道,它没有缓存空间,只有发送操作和接收操作同时准备好时,数据才能够安全地传输。
变量名 := make(chan 数据类型)
以下是一个示例代码,说明如何使用通道进行数据的发送和接收:
package main
import "fmt"
func main() {
// 创建一个缓冲大小为3的整型通道
ch := make(chan int, 3)
// 向通道发送数据
ch <- 1
ch <- 2
ch <- 3
// 从通道接收数据
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
输出结果:
1
2
3
在上面的代码中,我们首先创建了一个缓冲大小为3的整型通道ch
。然后通过ch <- 数据
的方式向通道发送数据。最后使用<-ch
的方式从通道接收数据,并将其打印输出。
需要注意的是,在通道中发送和接收数据都是阻塞的操作。当通道为空时,接收操作会被阻塞;当通道满时,发送操作会被阻塞。这种机制可以让我们有效地控制goroutine之间的同步。
Go语言的错误类型主要有两种:预定义错误类型和自定义错误类型。
(1) 以下是一个使用预定义错误类型的代码示例:
package main
import (
"errors"
"fmt"
)
func divide(x, y float64) (float64, error) {
if y == 0 {
return 0, errors.New("除数不能为零")
}
return x / y, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("发生错误:", err)
} else {
fmt.Println("计算结果:", result)
}
}
(2) 自定义错误类型: 在Go语言中,可以通过实现error接口来创建自定义的错误类型。自定义的错误类型需要实现Error()方法,该方法用于返回错误信息的字符串表示。可以根据需求添加自定义的字段和方法。
以下是一个使用自定义错误类型的代码示例:
package main
import (
"errors"
"fmt"
)
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return fmt.Sprintf("错误码:%d,错误信息:%s", e.Code, e.Message)
}
func divide(x, y float64) (float64, error) {
if y == 0 {
return 0, &MyError{Code: 1001, Message: "除数不能为零"}
}
return x / y, nil
}
func main() {
result, err := divide(10, 0)
if err != nil {
fmt.Println("发生错误:", err)
} else {
fmt.Println("计算结果:", result)
}
}
Go语言中的类型别名是指给一个已存在的类型定义一个新的名称,以便于在代码中更加清晰地表示类型的含义。类型别名不会创建新的类型,它只是为现有的类型提供了一个别名。
在Go语言中,可以使用type
关键字来定义类型别名。例如,我们可以将int
类型的别名定义为myInt
:
package main
import "fmt"
type myInt int
func main() {
var num myInt = 10
fmt.Println(num)
}
上面的代码中,我们定义了一个myInt
类型的别名,它实际上就是int
类型的别名。我们可以像使用int
类型一样使用myInt
类型,并且可以将myInt
类型的值赋给int
类型的变量,反之亦然。
类型别名在代码中可以增加可读性和语义,特别是当需要对相同类型的不同含义进行区分时特别有用。例如,在处理不同单位的长度时,可以使用类型别名来表示具体的含义。
除了基本类型之外,类型别名也可以应用于结构体、接口等复杂类型。下面是一个使用类型别名的结构体实例:
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee Person
func main() {
emp := Employee{
Name: "张三",
Age: 30,
}
fmt.Println(emp)
}
上面的代码中,我们定义了一个Person
结构体类型,然后使用Employee
类型别名来表示Person
类型。这样,我们可以用Employee
类型来创建结构体实例,其字段和方法与Person
类型完全相同。
通过类型别名,我们可以更好地区分不同的数据类型,提高代码的可读性和可维护性。需要注意的是,类型别名只在代码中有意义,编译后的程序并不会保留别名信息。
在Go语言中,类型转换是将一个类型的值转换为另一个类型的过程。类型转换通常用于不同类型之间的赋值或表达式的计算。
Go语言中的类型转换使用T(v)
的形式,其中T
表示要转换的目标类型,v
表示要转换的值。下面是几种基本类型的转换示例:
package main
import "fmt"
func main() {
var num int = 10
var flt float64 = float64(num) // int类型转换为float64类型
fmt.Println(flt)
var flt2 float64 = 3.14
var num2 int = int(flt2) // float64类型转换为int类型
fmt.Println(num2)
var char byte = 'A'
var num3 int = int(char) // byte类型转换为int类型
fmt.Println(num3)
}
上面的代码中,我们分别将整数类型、浮点数类型和字节类型进行了类型转换。需要注意的是,类型转换是有风险的,可能会导致数据丢失或溢出。例如,将一个较大的浮点数转换为整数类型时会丢失小数部分。
对于复杂类型,如结构体或自定义类型,类型转换需要满足两个类型之间的兼容性。如果两个类型的底层结构相同,可以使用强制类型转换来实现。以下是一个结构体类型转换的示例:
package main
import "fmt"
type Person struct {
Name string
Age int
}
type Employee struct {
Name string
Age int
Position string
}
func main() {
var p Person = Person{
Name: "张三",
Age: 30,
}
var emp Employee = Employee(p) // Person类型转换为Employee类型
fmt.Println(emp)
}
在上面的代码中,我们将一个Person
类型的值转换为了Employee
类型。由于两个结构体的底层结构相同,因此可以进行类型转换。
需要注意的是,类型转换是有限制的,在不同的类型之间不能进行随意的转换。只有在类型之间存在兼容性或底层结构相同的情况下,才能进行类型转换。否则,在进行类型转换时会出现编译错误。
在使用类型转换时,需要确保转换的安全性和准确性,并避免潜在的数据丢失或溢出问题。理解类型之间的兼容性和底层结构对类型转换非常重要。
Go语言的运算符包括以下几种:
以下是一些代码示例:
package main
import "fmt"
func main() {
a := 10
b := 20
// 算术运算符
fmt.Println(a + b) // 输出:30
fmt.Println(a - b) // 输出:-10
fmt.Println(a * b) // 输出:200
fmt.Println(a / b) // 输出:0
fmt.Println(a % b) // 输出:10
// 关系运算符
fmt.Println(a == b) // 输出:false
fmt.Println(a != b) // 输出:true
fmt.Println(a > b) // 输出:false
fmt.Println(a < b) // 输出:true
fmt.Println(a >= b) // 输出:false
fmt.Println(a <= b) // 输出:true
// 逻辑运算符
fmt.Println(true && false) // 输出:false
fmt.Println(true || false) // 输出:true
fmt.Println(!true) // 输出:false
// 位运算符
c := 5 // 二进制表示为 0101
d := 3 // 二进制表示为 0011
fmt.Println(c & d) // 按位与,输出:1(二进制 0001)
fmt.Println(c | d) // 按位或,输出:7(二进制 0111)
fmt.Println(c ^ d) // 按位异或,输出:6(二进制 0110)
fmt.Println(^d) // 按位取反,输出:-4(二进制 1100)
fmt.Println(c << 1) // 左移一位,输出:10(二进制 1010)
fmt.Println(c >> 1) // 右移一位,输出:2(二进制 0010)
// 赋值运算符
e := 10
e += 5 // 等价于 e = e + 5
fmt.Println(e) // 输出:15
// 其他运算符
f := 1
f++ // 等价于 f = f + 1
fmt.Println(f) // 输出:2
}
Go语言的输入输出涉及到多种方式和知识点,包括标准输入输出、文件操作、网络传输等。下面列举了一些常见的Go语言输入输出知识点和相应的代码实例:
fmt.Printf
和fmt.Println
函数打印输出到标准输出。示例代码:
package main
import "fmt"
func main() {
name := "John"
age := 25
fmt.Printf("My name is %s and my age is %d\n", name, age)
fmt.Println("This is a new line")
}
fmt.Scan
函数从标准输入读取数据,并存储到指定的变量中。示例代码:
package main
import "fmt"
func main() {
var name string
fmt.Print("Enter your name: ")
fmt.Scan(&name)
fmt.Printf("Hello, %s!\n", name)
}
os.Open
函数打开文件,并使用bufio.NewReader
函数创建一个带有缓冲区的读取器,然后通过读取器逐行读取文件内容。示例代码:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("data.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading file:", err)
}
}
net
包提供的相关函数进行网络通信,如net.Dial
、net.Listen
等。具体实现根据不同的场景而定。以上仅是一些常见的Go语言输入输出知识点和相应的代码示例,还有许多其他的输入输出方式和细节部分可以根据具体需求进行更深入的学习和探索。
Go语言的条件语句主要包括if语句和switch语句。下面是它们的知识点和代码实例:
代码实例:
package main
import "fmt"
func main() {
num := 10
if num < 0 {
fmt.Println("数字小于0")
} else if num > 0 {
fmt.Println("数字大于0")
} else {
fmt.Println("数字等于0")
}
}
代码实例:
package main
import "fmt"
func main() {
num := 2
switch num {
case 1:
fmt.Println("数字等于1")
case 2, 3:
fmt.Println("数字等于2或3")
default:
fmt.Println("数字不符合条件")
}
}
Go语言的循环语句主要包括for循环和range循环。下面是它们的知识点和代码实例:
代码实例:
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Println(i)
}
}
代码实例:
package main
import "fmt"
func main() {
arr := [3]string{"Go", "语言", "循环"}
for index, value := range arr {
fmt.Println(index, value)
}
}
3.跳转语句
Go语言提供了以下几种循环跳转语句:
break:用于终止当前循环,并跳出循环体。可以在for循环、switch语句和select语句中使用。
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 5 {
break
}
fmt.Println(i)
}
}
使用break语句跳出循环。
continue:用于跳过当前迭代,并进入下一次迭代。可以在for循环中使用。
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i%2 == 0 {
continue
}
fmt.Println(i)
}
}
在上述代码中,当i为偶数时,使用`continue`语句跳过当前迭代,直接进入下一次迭代
goto:用于无条件地跳转到指定标签的位置。可以在函数内部任意位置使用。
package main
import "fmt"
func main() {
for i := 0; i < 10; i++ {
if i == 5 {
goto end
}
fmt.Println(i)
}
end:
fmt.Println("Loop ended")
}
在上述代码中,当i等于5时,使用goto
语句跳转到end
标签处。
需要注意的是,使用goto
语句会使代码流程变得难以理解,容易产生混乱,所以应谨慎使用。
希望以上内容对你有所帮助,如果还有其他问题,请随时提问。
在Go语言中,包声明部分位于每个源文件的开头,并使用package
关键字进行声明。下面是包声明部分的知识点和代码实例:
package main
在上述代码中,main
是包名,表示该文件属于main
包。
import
关键字导入其他包,以便在当前文件中使用其他包提供的功能。可以导入标准库包、第三方包或自定义包。package main
import "fmt"
在上述代码中,通过import
导入了fmt
包,以便在代码中使用其提供的函数。
package main
import "example.com/mypackage"
func main() {
mypackage.MyFunction() // 可以访问MyFunction函数
// mypackage.myPrivateFunction() // 不能访问myPrivateFunction函数
}
在上述代码中,导入了名为mypackage
的自定义包,并可以使用其中的公开函数MyFunction
,但不能访问其中的私有函数myPrivateFunction
。
import
的别名来解决冲突。package main
import (
"fmt"
myfmt "example.com/mypackage/fmt"
)
func main() {
fmt.Println("Hello") // 输出:Hello(使用标准库的fmt包)
myfmt.Println("MyPackage") // 输出:MyPackage(使用自定义包的fmt包)
}
Go语言中的函数声明包括函数名称、参数列表、返回值列表和函数体。
函数名称是用来唯一标识函数的标识符。例如:
func add(a, b int) int {
return a + b
}
参数列表包括函数的输入参数,每个参数都由参数名称和参数类型组成,多个参数之间使用逗号分隔。函数在调用时需要传入实际参数。例如:
func add(a, b int) int { // a和b都是int类型的参数
return a + b
}
返回值列表定义函数的返回值类型,可以有多个返回值,每个返回值都由返回值类型声明。函数执行完毕后会返回相应的返回值。例如:
func divmod(a, b int) (int, int) { // 返回两个int类型的返回值
return a / b, a % b
}
函数体是函数的具体实现部分,包括函数内部的代码逻辑。函数体由一对花括号{}包围。例如:
func add(a, b int) int {
return a + b // 函数体中的代码逻辑
}
函数声明代码实例:
package main
import (
"fmt"
)
// 声明一个函数,计算两个数的和
func add(a, b int) int {
return a + b
}
func main() {
// 调用函数并打印结果
result := add(3, 4)
fmt.Println(result) // 输出:7
}
在Go语言中,函数参数可以通过值传递或引用传递的方式进行传递。
将参数的值复制一份,传递给函数进行操作,不会修改原始参数的值。示例代码如下:
package main
import "fmt"
// 值传递
func changeValue(a int) {
a = 10 // 修改函数内部的副本
}
func main() {
var num = 5
changeValue(num)
fmt.Println(num) // 输出:5,原始参数的值不变
}
将参数的内存地址传递给函数,函数可以直接操作原始参数的值。示例代码如下:
package main
import "fmt"
// 引用传递
func changeValue(a *int) {
*a = 10 // 修改原始参数的值
}
func main() {
var num = 5
changeValue(&num) // 传入num的地址
fmt.Println(num) // 输出:10,原始参数的值被修改
}
切片、映射和接口类型是引用类型,在函数间传递时,只复制了一个指向底层数据结构的指针,多个函数可以共享相同的底层数据。示例代码如下:
package main
import "fmt"
// 引用传递
func modifySlice(s []int) {
s[0] = 10 // 修改底层数组
}
func main() {
nums := []int{1, 2, 3}
modifySlice(nums)
fmt.Println(nums) // 输出:[10 2 3],原始参数的底层数组被修改
}
总结:
需要注意的是,在Go语言中,函数参数的传递方式默认是值传递,如果需要引用传递,需要使用指针作为参数。另外,切片、映射和接口类型默认是引用类型,无需额外操作即可实现引用传递。
Go语言的函数闭包是一种特殊的函数类型,它可以访问其词法环境中定义的变量。闭包在Go语言中被广泛用于实现函数式编程和并发编程。
闭包的基本概念:
闭包的特点:
下面是一个简单的示例代码,展示了闭包的使用方法:
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
a := adder()
fmt.Println(a(1)) // 输出结果:1
fmt.Println(a(2)) // 输出结果:3
fmt.Println(a(3)) // 输出结果:6
b := adder()
fmt.Println(b(10)) // 输出结果:10
fmt.Println(b(20)) // 输出结果:30
}
在上述代码中,函数adder是一个闭包函数。它定义了一个局部变量sum,并返回了一个匿名函数。这个匿名函数可以访问和修改adder函数的局部变量sum。在main函数中我们创建了两个闭包实例a和b,它们拥有独立的sum变量。当调用闭包实例时,sum会持续累加传入的参数。
上述示例中,第一次调用闭包实例a时,sum初始化为0,之后每次调用闭包函数时,sum都会被累加。而第一次调用闭包实例b时,sum又重新初始化为0,因此与实例a完全独立。
闭包函数的应用场景包括但不限于:
Go语言中的一个关键字,用于延迟函数的执行。它常用于在函数返回之前释放资源、关闭文件或者解锁资源等操作。下面是关于defer语句的一些知识点:
下面是一些defer语句的代码实例:
func openFile() {
file := open("file.txt")
defer file.Close() // 在函数返回前关闭文件
// 使用文件进行读写操作
}
func measureTime() {
defer timeTrack(time.Now(), "FunctionName") // 在函数返回前计算运行时间
// 函数逻辑代码
}
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
log.Printf("%s took %s", name, elapsed)
}
func lockUnlock() {
mu.Lock()
defer mu.Unlock() // 在函数返回前释放锁定
// 临界区逻辑代码
}
func openFile() error {
file, err := open("file.txt")
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil { // 在函数返回前关闭文件,并处理关闭过程中的错误
log.Println(err)
}
}()
// 使用文件进行读写操作
return nil
}
Go语言中的结构体是一种用户自定义的复合数据类型,用于存储不同类型的数据字段。结构体由一系列字段组成,每个字段都有自己的名称和类型。
下面是关于Go语言结构体的一些常见知识点以及相应的代码示例:
结构体类型的定义使用关键字type
,后面跟着结构体的名称以及字段列表。字段列表由字段名和字段类型组成,多个字段之间使用逗号分隔。
type Person struct {
Name string
Age int
}
可以使用var
关键字声明一个结构体变量,并使用结构体名称{}
的形式进行初始化。
var p Person
p.Name = "Alice"
p.Age = 20
也可以直接在声明时进行初始化:
p := Person{Name: "Bob", Age: 25}
可以使用.
操作符来访问结构体实例中的字段。
fmt.Println(p.Name)
fmt.Println(p.Age)
可以通过&
操作符获取结构体的指针,通过指针修改结构体中的字段值。
p := &Person{Name: "Cathy", Age: 30}
p.Name = "David"
可以在声明结构体变量时直接定义一个匿名的结构体。
p := struct {
Name string
Age int
}{Name: "Eva", Age: 35}
可以在结构体中嵌套其他结构体,形成更复杂的数据结构。
type Address struct {
City string
State string
}
type Person struct {
Name string
Age int
Address Address
}
p := Person{
Name: "Frank",
Age: 40,
Address: Address{
City: "New York",
State: "NY",
},
}
fmt.Println(p.Address.City)
可以为结构体定义方法,可以在方法内部访问结构体的字段和调用其他函数。
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
rect := Rectangle{Width: 10, Height: 5}
fmt.Println(rect.Area())
以上是关于Go语言结构体的一些知识点和代码示例。需要注意的是,在实际开发中还有更多高级的用法和技巧,可以根据具体需求进行深入学习和探索。
Go语言的接口(interface)是一种定义了一组方法的类型。接口类型是一种抽象的类型,它不会提供具体的实现,只定义了方法的签名。任何实现了接口中所有方法的类型都被称为实现了该接口。
下面是关于Go语言接口的知识点以及一个简单的代码实例:
type
关键字定义接口类型,例如:type Writer interface {}
type
关键字定义类型,并在类型内实现接口中定义的所有方法下面是一个简单的代码实例,演示了接口的定义、实现和使用:
package main
import "fmt"
// 定义一个接口
type Shape interface {
Area() float64
}
// 定义一个结构体类型
type Circle struct {
radius float64
}
// 实现Shape接口的Area方法
func (c Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
// 定义另一个结构体类型
type Rectangle struct {
width, height float64
}
// 实现Shape接口的Area方法
func (r Rectangle) Area() float64 {
return r.width * r.height
}
func main() {
// 声明一个接口类型的变量
var s Shape
// 创建一个Circle类型的变量,并赋值给接口变量s
c := Circle{radius: 5}
s = c
// 调用接口变量的方法
fmt.Println("圆的面积:", s.Area())
// 创建一个Rectangle类型的变量,并赋值给接口变量s
r := Rectangle{width: 4, height: 3}
s = r
// 调用接口变量的方法
fmt.Println("矩形的面积:", s.Area())
}
在上面的代码中,Shape接口定义了一个方法Area()
。Circle和Rectangle分别实现了Shape接口中的Area()
方法,它们都是Shape接口的实现类型。在main函数中,通过接口变量s来调用不同类型的方法,实现了对应类型的功能。
在Go语言中,反射(reflection)是一种在运行时动态检查对象类型和值的机制。使用反射,我们可以在不知道具体类型的情况下,操作和查询对象的属性、方法和类型信息。下面是Go语言反射的知识点及代码实例。
在Go语言中,反射通过reflect
包来实现。reflect
包提供了Type
和Value
两个结构体,分别用于表示类型和值的信息。
通过reflect.TypeOf()
函数可以获取一个值的类型信息。该函数返回一个reflect.Type
类型的值,代表对象的类型。
package main
import (
"fmt"
"reflect"
)
func main() {
str := "Hello, reflection!"
t := reflect.TypeOf(str)
fmt.Println(t) // 输出: string
}
通过reflect.ValueOf()
函数可以获取一个值的值信息。该函数返回一个reflect.Value
类型的值,代表对象的值。
package main
import (
"fmt"
"reflect"
)
func main() {
num := 42
v := reflect.ValueOf(num)
fmt.Println(v) // 输出: 42
}
通过reflect.Value
提供的Set()
方法,可以通过反射修改一个变量的值。
package main
import (
"fmt"
"reflect"
)
func main() {
num := 42
v := reflect.ValueOf(&num) // 使用指针,才能修改值
p := v.Elem()
p.SetInt(100)
fmt.Println(num) // 输出: 100
}
通过reflect.Value
提供的MethodByName()
和Call()
方法,可以通过反射调用一个对象的方法。
package main
import (
"fmt"
"reflect"
)
type MyStruct struct {
Name string
}
func (ms MyStruct) SayHello() {
fmt.Println("Hello from", ms.Name)
}
func main() {
ms := MyStruct{Name: "Alice"}
v := reflect.ValueOf(ms)
method := v.MethodByName("SayHello")
method.Call(nil) // 输出: Hello from Alice
}
通过反射,我们可以动态地获取对象的类型和值信息,并在运行时进行操作。但是需要注意的是,反射的使用会带来一定的性能开销,因此在性能要求较高的场景下需要慎重使用。
fmt包是Go语言中用于格式化输入输出的标准库,提供了丰富的函数和方法来对数据进行格式化、打印和读取。下面是fmt包的一些常用知识点以及相应的代码实例:
fmt.Print("Hello, World!")
name := "Alice"
age := 28
fmt.Printf("Name: %s, Age: %d", name, age)
fmt.Println("Hello,", "World!")
var name string
fmt.Scan(&name)
var name string
input := "Alice Smith"
fmt.Sscanf(input, "%s %s", &firstName, &lastName)
age := 28
name := "Alice"
fmt.Printf("Name: %s, Age: %d", name, age)
value := 3.1415926
fmt.Printf("Value: %.2f", value) // 输出:Value: 3.14
name := "Alice"
age := 28
result := fmt.Sprintf("Name: %s, Age: %d", name, age)
math包是Go语言中用于数学计算的标准库,提供了许多数学函数和常量。下面是math包的一些常用知识点以及相应的代码实例:
fmt.Println(math.Pi) // 输出:3.141592653589793
num := -10
result := math.Abs(num)
fmt.Println(result) // 输出:10
num := 3.14
result := math.Ceil(num)
fmt.Println(result) // 输出:4
num := 3.14
result := math.Floor(num)
fmt.Println(result) // 输出:3
num := 3.54
result := math.Round(num)
fmt.Println(result) // 输出:4
num1 := 10
num2 := 20
result := math.Max(num1, num2)
fmt.Println(result) // 输出:20
num1 := 10
num2 := 20
result := math.Min(num1, num2)
fmt.Println(result) // 输出:10
base := 2
exponent := 3
result := math.Pow(base, exponent)
fmt.Println(result) // 输出:8
num := 16
result := math.Sqrt(num)
fmt.Println(result) // 输出:4
num := 3.14
result := math.Trunc(num)
fmt.Println(result) // 输出:3
strings包是Go语言中用于字符串处理的标准库,提供了许多字符串相关的函数。下面是strings包的一些常用知识点以及相应的代码实例:
str1 := "hello"
str2 := "world"
result := strings.Compare(str1, str2)
fmt.Println(result) // 输出:-1 (str1 < str2)
str1 := "hello"
str2 := "world"
result := strings.Concat(str1, str2)
fmt.Println(result) // 输出:helloworld
str := "hello world"
prefix := "hello"
result := strings.HasPrefix(str, prefix)
fmt.Println(result) // 输出:true
str := "hello world"
suffix := "world"
result := strings.HasSuffix(str, suffix)
fmt.Println(result) // 输出:true
str := "hello world"
substr := "world"
result := strings.Index(str, substr)
fmt.Println(result) // 输出:6
str := "hello world"
substr := "o"
result := strings.LastIndex(str, substr)
fmt.Println(result) // 输出:7
str := "hello,world"
sep := ","
result := strings.Split(str, sep)
fmt.Println(result) // 输出:[hello world]
strSlice := []string{"hello", "world"}
sep := ","
result := strings.Join(strSlice, sep)
fmt.Println(result) // 输出:hello,world
str := "hello world"
old := "world"
new := "golang"
result := strings.Replace(str, old, new, -1)
fmt.Println(result) // 输出:hello golang
str := " hello world "
cutset := " "
result := strings.Trim(str, cutset)
fmt.Println(result) // 输出:hello world
bytes包是Go语言中的一个标准库,提供了对字节处理的一系列函数和方法。以下是bytes包的一些常见知识点以及代码实例:
func Compare(a, b []byte) int
:比较两个字节切片 a 和 b,返回一个整数表示它们的大小关系。 示例:
a := []byte("hello")
b := []byte("world")
result := bytes.Compare(a, b)
fmt.Println(result) // 输出结果为-1,表示a小于b
func Contains(b, subslice []byte) bool
:判断字节切片 b 中是否包含子切片 subslice。 示例:
b := []byte("hello world")
subslice := []byte("world")
result := bytes.Contains(b, subslice)
fmt.Println(result) // 输出结果为true
func Count(s, sep []byte) int
:统计字节切片 s 中非重叠出现的 sep 的次数。 示例:
s := []byte("hello hello hello")
sep := []byte("hello")
result := bytes.Count(s, sep)
fmt.Println(result) // 输出结果为3
func Index(s, sep []byte) int
:返回字节切片 s 中第一次出现 sep 的索引位置,如果找不到则返回 -1。 示例:
s := []byte("hello world")
sep := []byte("world")
result := bytes.Index(s, sep)
fmt.Println(result) // 输出结果为6
func Join(s [][]byte, sep []byte) []byte
:使用 sep 作为分隔符,将二维字节切片 s 进行拼接。 示例:
s := [][]byte{[]byte("hello"), []byte("world")}
sep := []byte(", ")
result := bytes.Join(s, sep)
fmt.Println(string(result)) // 输出结果为"hello, world"
func Split(s, sep []byte) [][]byte
:将字节切片 s 按照 sep 进行分割,并返回二维字节切片。 示例:
s := []byte("hello,world")
sep := []byte(",")
result := bytes.Split(s, sep)
for _, v := range result {
fmt.Println(string(v))
}
// 输出结果为:
// hello
// world
func Replace(s, old, new []byte, n int) []byte
:将字节切片 s 中的前 n 个 old 替换为 new。 示例:
s := []byte("hello hello hello")
old := []byte("hello")
new := []byte("hi")
result := bytes.Replace(s, old, new, 2)
fmt.Println(string(result)) // 输出结果为"hi hi hello"
func ToUpper(s []byte) []byte
:将字节切片 s 中的所有字符转换为大写。 示例:
s := []byte("hello world")
result := bytes.ToUpper(s)
fmt.Println(string(result)) // 输出结果为"HELLO WORLD"
以上仅是bytes包中一部分常用函数的示例,还有更多的函数和方法可供使用。你可以通过查阅Go语言官方文档来获取更详细的信息:https://golang.org/pkg/bytes/
Go语言的内存分配机制主要有以下几个方面的知识点:
垃圾回收(Garbage Collection,GC):Go语言使用自动垃圾回收来管理内存,它会在程序运行过程中自动进行内存回收,释放不再使用的对象占用的内存空间。
栈和堆:Go语言的内存管理采用了栈和堆的方式。栈是用来存放函数调用时的局部变量和函数返回值等临时数据的区域,它的空间是连续的。堆是用来存放动态分配的内存空间的区域,它的空间是离散的。
栈上分配(Stack Allocation):Go语言对于一些较小的对象,会直接在栈上分配内存空间,这样可以减少内存的分配和释放的开销。
堆上分配(Heap Allocation):对于大对象或者生命周期较长的对象,Go语言会在堆上分配内存空间,并由垃圾回收器来负责回收这部分内存。
引用计数(Reference Counting):除了垃圾回收外,Go语言的内存管理还会使用引用计数的方法来辅助垃圾回收。通过记录对象被引用的次数,当引用计数为零时可以确定对象不再被使用,从而释放其占用的内存空间。
以下是一个简单的Go语言代码示例,展示了内存分配和回收的操作:
package main
import "fmt"
func main() {
// 分配一个整型变量到栈上
x := 10
fmt.Println(x) // 输出:10
// 分配一个整型指针到堆上
y := new(int)
*y = 20
fmt.Println(*y) // 输出:20
// 将堆上分配的内存释放掉
// 在Go语言中,不需要手动释放堆上分配的内存,垃圾回收器会自动处理
}
在这个示例中,变量 x
是直接分配在栈上的,而变量 y
是通过 new()
函数分配到堆上的。当不再需要 y
变量时,不需要手动释放内存,垃圾回收器会自动回收这部分内存。
Go语言提供了一些与内存操作相关的函数,用于对内存进行分配、释放和操作。以下是一些常用的内存函数及其知识点以及代码实例:
make 函数:
用于创建切片、映射和通道等引用类型的对象,并进行内存分配。make函数会返回一个已分配内存的值,并初始化为默认值。 示例:
slice := make([]int, 5, 10) // 创建一个切片,长度为5,容量为10
new 函数:
用于分配内存空间,返回指向该内存空间的指针。new函数返回的指针指向已分配但未初始化的零值。 示例:
ptr := new(int) // 分配一个int类型大小的内存空间,并返回指向该空间的指针
append 函数:
用于向切片追加元素,并在需要时进行内存扩容。如果切片的容量不足以容纳新元素,append函数会自动重新分配更大的内存空间。 示例:
slice := []int{1, 2, 3}
slice = append(slice, 4) // 向切片追加一个元素
copy 函数:
用于将源切片的内容复制到目标切片。copy函数会根据目标切片的容量来确定要复制的元素个数。 示例:
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // 将src切片的内容复制到dst切片
delete 函数:
用于从映射中删除指定键值对。 示例:
m := map[string]int{
"apple": 5,
"banana": 3,
}
delete(m, "apple") // 从映射m中删除键"apple"
unsafe 包:
提供了一些用于进行底层内存操作的函数,但使用不当可能会导致不安全和不可预测的行为。在普通的应用程序开发中,尽量避免使用unsafe包。 示例:
import "unsafe"
type Person struct {
Name string
Age int
}
p := &Person{Name: "Alice", Age: 20}
namePtr := (*string)(unsafe.Pointer(p)) // 将Person类型的指针转换为string类型的指针
并发编程是指在程序中同时执行多个独立的任务或操作的能力。Go语言提供了丰富的并发编程支持,包括goroutine和channel等特性。下面是一些常见的并发编程知识点和代码实例:
func main() {
go fun1() // 启动一个goroutine
fun2()
}
func fun1() {
// goroutine的逻辑
}
func fun2() {
// 主线程的逻辑
}
func main() {
ch := make(chan int) // 创建一个int类型的channel
go func() {
ch <- 42 // 发送数据到channel
}()
value := <-ch // 从channel接收数据
fmt.Println(value)
}
func main() {
ch1 := make(chan int)
ch2 := make(chan string)
go func() {
time.Sleep(time.Second)
ch1 <- 42
}()
go func() {
time.Sleep(time.Second * 2)
ch2 <- "Hello"
}()
select {
case value := <-ch1:
fmt.Println("Received from ch1:", value)
case message := <-ch2:
fmt.Println("Received from ch2:", message)
}
}
import (
"sync"
)
var count int
var mutex sync.Mutex
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go increment(&wg)
}
wg.Wait()
fmt.Println("Count:", count)
}
func increment(wg *sync.WaitGroup) {
mutex.Lock()
defer mutex.Unlock()
count++
wg.Done()
}
Go语言对并发编程提供了丰富的支持,包括多进程和多线程。下面是Go语言中相关的知识点以及代码示例:
go
可以启动一个goroutine。package main
import (
"fmt"
"os"
)
func main() {
cmd := "/bin/ls"
args := []string{"-l", "-a"}
// 创建子进程
processAttr := &os.ProcAttr{
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
}
process, err := os.StartProcess(cmd, args, processAttr)
if err != nil {
fmt.Println("Error:", err)
return
}
// 等待子进程退出
state, err := process.Wait()
if err != nil {
fmt.Println("Wait error:", err)
return
}
fmt.Println("Exit status:", state.ExitCode())
}
go
启动一个goroutine,可以在函数调用前添加该关键字即可。package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Printf("Worker %d starting\n", id)
// 模拟工作
for i := 0; i < 5; i++ {
fmt.Printf("Worker %d working on task %d\n", id, i)
}
fmt.Printf("Worker %d completed\n", id)
}
func main() {
var wg sync.WaitGroup
// 启动多个goroutine
for i := 0; i < 3; i++ {
wg.Add(1)
go worker(i, &wg)
}
// 等待所有goroutine完成
wg.Wait()
fmt.Println("All workers completed")
}
Go语言的网络编程主要涉及以下几个知识点:
Go语言提供了net
包来支持TCP和 UDP套接字编程。常用的函数有Listen
、Dial
、Accept
、Connect
等,可以通过这些函数创建和管理套接字连接。以下是一个TCP服务器的代码示例:
package main
import (
"fmt"
"net"
)
func handleConnection(conn net.Conn) {
buffer := make([]byte, 1024)
_, err := conn.Read(buffer)
if err != nil {
fmt.Println("Error reading:", err.Error())
return
}
fmt.Println("Received message:", string(buffer))
conn.Close()
}
func main() {
listener, err := net.Listen("tcp", "localhost:8888")
if err != nil {
fmt.Println("Error listening:", err.Error())
return
}
defer listener.Close()
fmt.Println("Server started, listening on localhost:8888")
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("Error accepting connection:", err.Error())
return
}
go handleConnection(conn)
}
}
Go语言内置支持HTTP服务器,使用net/http
包可以轻松创建一个HTTP服务器并处理HTTP请求。以下是一个简单的HTTP服务器代码示例:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Error starting server:", err.Error())
return
}
}
Go语言通过github.com/gorilla/websocket
包提供了对WebSocket的支持。可以使用该包创建WebSocket服务器和客户端,并进行双向通信。以下是一个简单的WebSocket服务器代码示例:
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
func echo(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
log.Println(err)
return
}
fmt.Println("Received message:", string(message))
err = conn.WriteMessage(messageType, message)
if err != nil {
log.Println(err)
return
}
}
}
func main() {
http.HandleFunc("/ws", echo)
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("Error starting server:", err)
}
}
以上是Go语言网络编程的一些常用知识点和简单示例。除了TCP/UDP、HTTP和WebSocket,Go语言还提供了一些其他网络编程相关的功能和包,如TLS/SSL、FTP、SMTP等。