10.函数
(1)函数的定义
在Go语言中,函数是一段执行特定任务的代码块。函数的基本格式如下:
func functionName(parameterList) returnType {
// 函数体
return result
}
其中,func是定义函数的关键字,functionName是函数名,parameterList是参数列表(可以为空),returnType是返回值类型(可以是单个类型,也可以是多个类型用括号括起来),函数体是函数的具体执行代码,return语句用于返回结果。例如,一个简单的加法函数可以这样定义:
func add(num1, num2 int) int {
return num1 + num2
}
(2)函数参数
值传递:Go语言中函数参数默认是值传递。这意味着当把一个变量作为参数传递给函数时,函数会得到这个变量的一个副本,函数内部对参数的修改不会影响到原始变量。例如:
num = 100
}
func main() {
myNum := 10
modifyValue(myNum)
println(myNum)
}
在这个例子中,modifyValue函数内部修改了num的值,但main函数中的myNum的值不变,因为传递给modifyValue函数的是myNum的副本。
引用传递(指针参数):如果想要在函数内部修改原始变量,可以使用指针作为参数。例如:
*num = 100
}
func main() {
myNum := 10
modifyValueByPointer(&myNum)
println(myNum)
}
这里num是一个指向整数的指针,*num表示解引用指针获取指针指向的值。通过传递变量的地址&myNum给函数,函数内部就可以修改原始变量的值。
(3)函数返回值
单个返回值:如前面的加法函数add,它返回一个整数类型的结果。可以在调用函数后接收返回值并使用,例如:
func main() {
result := add(3, 5)
println(result)
}
多个返回值:Go语言的函数可以有多个返回值。例如,一个函数可以同时返回除法运算的商和余数:
func divide(num1, num2 int) (int, int) {
quotient := num1 / num2
remainder := num1 % num2
return quotient, remainder
}
func main() {
q, r := divide(7, 3)
println("商:", q, "余数:", r)
}
当函数有多个返回值时,调用函数时可以使用多个变量来接收返回值。并且,返回值可以命名,就像在divide函数中对quotient和remainder进行命名一样,这可以提高代码的可读性,并且在函数内部可以直接使用这些命名的返回值变量,就像普通变量一样。
(4)函数变量(函数作为值)
在Go语言中,函数可以作为值进行传递。这意味着可以把函数赋值给一个变量,然后通过这个变量来调用函数。例如:
println("Hello!")
}
func main() {
myFunction := sayHello
myFunction()
}
这里myFunction变量被赋值为sayHello函数,然后可以像调用普通函数一样调用myFunction,它会执行sayHello函数的代码。这种特性使得Go语言可以实现函数式编程的一些模式,比如回调函数等。
11.指针
(1)指针的基本概念
指针是一种存储变量内存地址的数据类型。在Go语言中,每个变量在内存中都有一个地址,指针可以用来获取和操作这个地址。通过指针,可以间接地访问和修改它所指向的变量的值。
例如,对于一个整数变量var num int = 10,可以定义一个指针来指向这个变量。指针变量的类型是*int(表示指向整数的指针),可以使用&操作符来获取变量的地址。如var p *int = &num,这里p就是一个指向num的指针。
(2)指针的操作
获取变量的地址:使用&操作符获取变量的地址。例如,var num int = 10; p := &num,这里p存储了num的内存地址。
访问指针指向的值:使用*操作符(解引用操作符)来访问指针指向的变量的值。例如,对于上面的p,*p就可以获取p所指向的变量的值,即num的值。所以println(*p)会输出10。
修改指针指向的值:通过解引用指针并赋值,可以修改指针指向的变量的值。例如,*p = 20会将num的值修改为20,因为p指向num。
(3)指针作为函数参数
如前面在函数部分提到的,Go语言函数参数默认是值传递。但如果想要在函数内部修改外部变量的值,可以使用指针作为参数。 - 例如,考虑一个函数,用于交换两个整数的值:
temp := *num1
*num1 = *num2
*num2 = temp
}
func main() {
a, b := 10, 20
swap(&a, &b)
println(a, b)
}
在这个swap函数中,参数num1和num2是指向整数的指针。通过解引用这些指针,可以在函数内部交换它们所指向的变量的值。在main函数中,通过传递a和b的地址&a和&b给swap函数,实现了a和b值的交换。
(4)指针与数组
数组指针:可以定义一个指针来指向数组。例如,var arr [3]int = [3]int{1, 2, 3}; var p *[3]int = &arr,这里p是一个指向包含3个整数的数组的指针。
通过指针访问数组元素:可以通过指针和索引来访问数组元素。例如,对于上面的p,(*p)[0]表示访问p所指向的数组的第一个元素,等价于arr[0]。
指针与切片:切片在底层是一个包含指向底层数组的指针、长度和容量的结构体。当把切片作为参数传递时,实际上是传递了这个结构体的副本,但由于切片包含了底层数组的指针,所以在函数内部可以通过这个指针访问和修改底层数组的元素。例如: go func modifySlice(slice []int) { slice[0] = 100 } func main() { mySlice := []int{1, 2, 3} modifySlice(mySlice) println(mySlice[0]) } - 在这个例子中,虽然mySlice本身是值传递给modifySlice函数的,但因为切片包含底层数组的指针,所以函数内部可以修改底层数组的元素,从而影响到原始切片。
12.结构体
(1)定义与基本概念
结构体(struct)是Go语言中一种用户自定义的数据类型,用于将不同类型的数据组合成一个有机的整体。它就像是一个数据的容器,把多个相关的变量打包在一起,方便管理和操作。
例如,定义一个表示学生信息的结构体:
type Student struct {
Name string
Age int
Grade int
Subject []string
}
这里定义了一个名为Student的结构体类型,它包含了学生的姓名(Name,字符串类型)、年龄(Age,整数类型)、年级(Grade,整数类型)和学习科目(Subject,字符串切片类型)。
(2)结构体变量的创建和初始化
按顺序初始化:可以按照结构体中字段定义的顺序来初始化结构体变量。例如:
go student1 := Student{"Tom", 18, 12, []string{"Math", "English"}}
指定字段初始化:也可以通过指定字段名来初始化结构体变量,这种方式不要求按照顺序。例如:
student1 := Student{"Tom", 18, 12, []string{"Math", "English"}}student2 := Student{
Age: 17,
Name: "Jerry",
Grade: 11,
Subject: []string{"Physics", "Chemistry"},
}
使用new函数创建: new函数返回一个指向结构体的指针。例如:
student3Ptr.Name = "Alice"
student3Ptr.Age = 16
student3Ptr.Grade = 10
student3Ptr.Subject = []string{"Biology"}
这里student3Ptr是一个指向Student结构体的指针。通过指针访问结构体的字段需要使用(*student3Ptr).Name这样的语法,不过Go语言也允许使用student3Ptr.Name这种更简洁的方式(这是Go语言的语法糖)。
(3)结构体的嵌套
结构体可以嵌套其他结构体,用于构建更复杂的数据结构。例如,定义一个包含学生家庭住址的结构体,并且将其嵌套在学生结构体中:
type Address struct {
City string
Street string
ZipCode int
}
type StudentWithAddress struct {
Student
HomeAddress Address
}
可以这样初始化和访问嵌套结构体的变量:
student4 := StudentWithAddress{
Student: Student{
Name: "Bob",
Age: 19,
Grade: 13,
Subject: []string{"History", "Geography"},
},
HomeAddress: Address{
City: "New York",
Street: "Broadway",
ZipCode: 10001,
},
}
fmt.Println(student4.Name)
fmt.Println(student4.HomeAddress.City)
(4)结构体的方法
结构体可以有与之关联的方法,方法类似于函数,但它有一个接收者(receiver),用于指定这个方法是属于哪个结构体类型的。例如,为Student结构体添加一个方法来打印学生信息:
fmt.Printf("Name: %s, Age: %d, Grade: %d, Subjects: %v\n", s.Name, s.Age, s.Grade, s.Subject)
}
可以通过结构体变量来调用这个方法:
go student1.PrintInfo()
方法的接收者可以是值类型(如上面的s Student),也可以是指针类型(如(s *Student))。如果方法需要修改结构体的内容,通常使用指针类型的接收者,这样可以避免在方法调用时复制整个结构体。例如:
func (s *Student) AddSubject(newSubject string) {
s.Subject = append(s.Subject, newSubject)
}
(5)结构体的比较
如果结构体的所有字段都是可比较的类型(如基本类型、字符串等),那么结构体变量可以使用==和!=进行比较。比较是按字段逐个进行的。例如:
student5 := Student{"Tom", 18, 12, []string{"Math", "English"}}
student6 := Student{"Tom", 18, 12, []string{"Math", "English"}}
if student5 == student6 {
fmt.Println("The two students are the same.")
}
但是,如果结构体包含不可比较的字段(如切片、映射等引用类型),则不能直接使用==和!=来比较结构体变量。在这种情况下,需要自定义比较逻辑,例如比较关键字段或者实现一个Equal方法来进行比较。
14.错误处理
(1)错误处理的基本概念
在Go语言中,错误处理是一种重要的编程实践。当一个函数可能出现错误时,它通常会返回一个错误值,调用者有责任检查并处理这个错误。错误在Go语言中是一个接口类型,它定义了一个Error方法,返回一个字符串,用于描述错误信息。
(2)错误的表示和返回
自定义错误:可以通过实现error接口来创建自定义错误。例如:
msg string
}
func (e MyError) Error() string {
return e.msg
}
这样就定义了一个MyError类型,它实现了error接口。可以在函数中返回这个自定义错误,如:
func Divide(num1, num2 int) (int, error) {
if num2 == 0 {
return 0, MyError{"除数不能为0"}
}
return num1 / num2, nil
}
标准库中的错误类型:Go语言的标准库也提供了许多预定义的错误类型,如os.Open函数在文件打开失败时会返回一个os.PathError类型的错误,net.Dial函数在网络连接失败时会返回一个net.Error类型的错误等。
(3)检查和处理错误
在调用可能返回错误的函数后,应该立即检查错误并进行适当的处理。例如:
if err!= nil {
fmt.Println("发生错误:", err)
return
}
fmt.Println("结果:", result)
通常,nil值表示没有错误,非nil值表示出现了错误。处理错误的方式可以有多种,包括打印错误信息、返回错误给上层调用者、进行重试等。
15字符串操作
(1)字符串的基本操作
定义字符串:在Go语言中,可以使用双引号(")来定义字符串。例如:str := "Hello, World!"。字符串是不可变的,一旦创建,就不能修改其内容。
获取字符串长度:使用len函数可以获取字符串的长度,它返回字符串中字节的数量。例如,对于UTF - 8编码的字符串str := "你好",len(str)的值为6,因为UTF - 8编码下,“你好”这两个汉字占用6个字节。
访问字符串中的字符:可以通过索引来访问字符串中的字节。例如,str := "abc"; println(str[0])会打印出字符a的ASCII码值(97)。需要注意的是,这种方式适用于ASCII字符,对于非ASCII字符(如汉字),由于UTF - 8编码的复杂性,直接通过索引访问可能会得到意外的结果。
(2)字符串拼接
使用+运算符拼接:可以使用+运算符来拼接字符串。例如:str1 := "Hello"; str2 := " World"; result := str1 + str2; println(result),会输出Hello World。不过,这种方式在拼接大量字符串时效率较低,因为每次拼接都会创建一个新的字符串对象。
使用fmt.Sprintf函数拼接:fmt.Sprintf函数可以按照指定的格式将多个值转换为字符串并拼接在一起。例如:name := "Alice"; age := 25; str := fmt.Sprintf("My name is %s and I'm %d years old.", name, age),这种方式在格式化输出时非常有用。
使用strings.Builder拼接(高效方式):strings.Builder是一个用于高效构建字符串的结构体。例如:
var builder strings.Builder
builder.WriteString("Hello")
builder.WriteString(" World")
result := builder.String()
println(result)
这种方式在需要频繁拼接字符串时效率更高,因为它避免了多次创建新的字符串对象,而是在一个可变的缓冲区中构建字符串。
(3)字符串比较
使用==和!=比较:可以使用==和!=运算符来比较两个字符串是否相等或不相等。例如:str1 := "abc"; str2 := "abc"; if str1 == str2 { println("Equal") }。比较是基于字节序列进行的,对于UTF - 8编码的字符串,会逐个字节比较。
使用strings.Compare函数比较:strings.Compare函数也可以用于比较两个字符串。它返回一个整数,当返回值为0时,表示两个字符串相等;当返回值小于0时,表示第一个字符串小于第二个字符串;当返回值大于0时,表示第一个字符串大于第二个字符串。例如:result := strings.Compare("abc", "abd"); if result < 0 { println("abc is less than abd") }。
(4)字符串查找和替换
查找子字符串:使用strings.Contains函数可以检查一个字符串是否包含另一个子字符串。例如:str := "Hello, World"; if strings.Contains(str, "World") { println("包含") }。 - 使用strings.Index函数可以查找子字符串在字符串中的首次出现位置。例如:str := "abcdef"; index := strings.Index(str, "cd"); println(index)会输出2,因为“cd”在“abcdef”中的起始位置是2。 - strings.LastIndex函数则用于查找子字符串在字符串中的最后一次出现位置。
替换子字符串:使用strings.Replace函数可以替换字符串中的子字符串。例如:str := "Hello, World"; newStr := strings.Replace(str, "World", "Go", 1); println(newStr),会将str中的第一个“World”替换为“Go”,输出Hello, Go。如果最后一个参数(替换次数)为-1,则会替换所有匹配的子字符串。
16.字符串格式化
%v:打印任意类型的参数 %+v:详细 %#v:更详细 %.2f:保留两位小数
17.JSON处理
字段名大写即可使用json
18时间处理
time.Now 使用特定时间进行格式化时间
time.Unix:获取时间戳
19.数字解析
strconv包
10:进制
0:自动推测
64:64位精度
20:进程信息
os,os/exec包
os.Args:获取进程执行时的命令行参数
Getenv:获取环境变量
SetGenv:写入环境变量