前端尝试转GO学习(第三天)
指针
指针可以创建变量,称为指针变量。指针变量的类型为 *Type,该指针指向一个 Type 类型的变量。指针变量会存储的某个实际变量的内存地址,通过记录变量的地址,间接的操作该变量。
创建指针
定义变量,之后通过获取变量的地址创建指针
func pointFunc() {
// 变量 p
p := "学习"
// 取变量 p 的地址创建指针 point
point := &p
fmt.Print(point) // 0xc000096210
}
先创建指针并分配好内存,再给指针指向的内存地址写入对应的值
// 创建指针
p1 := new(string)
// 给指针指向的内存地址写入对应的值
*p1 = "Go 语言学习"
fmt.Println(p1)
fmt.Println(*p1)
声明指针变量,再从其他变量获取内存地址给指针变量
// 定义变量 x2
x2 := "React"
// 声明指针变量
var p *string
// 指针初始化
p = &x2
fmt.Println(p)
x := "学习Go"
ptr := &x
fmt.Println("x = ", x) // x = 面向加薪学习
fmt.Println("*ptr = ", *ptr) // *p = 面向加薪学习
fmt.Println("&x = ", &x) // &x = 0xc000010270
fmt.Println("ptr = ", ptr) // p = 0xc000010270
&:从一个变量中取到其内存地址。
*:如果在赋值操作值的左边,指该指针指向的变量;如果在赋值操作符的右边,指从一个指针变量中取得变量值,又称指针的解引用。
指针的类型
*(指向变量值的数据类型) 就是对应的指针类型。
func pointerType() {
testStr := "Go"
testIntNumber := 1
testBool := false
testFloatNumber := 3.2
fmt.Printf("type of &mystr is :%T\n", &testStr) // *string
fmt.Printf("type of &myint is :%T\n", &testIntNumber) // *int
fmt.Printf("type of &mybool is :%T\n", &testBool) // *bool
fmt.Printf("type of &myfloat is :%T\n", &testFloatNumber) // *float64
}
指针的零值
如果指针声明后没有进行初始化,其默认零值是 nil
func zeroPointer() {
var zeroPoint *int
fmt.Println(zeroPoint) // <nil>
}
函数传递指针参数
在函数中对指针参数所做的修改,在函数返回后会保存相应的修改。
func changeByPointer(value *int) {
*value = 200
}
func funcPoint() {
test01 := 1
point01 := &test01
fmt.Println("before", *point01) // 1
changeByPointer(point01)
fmt.Println("after", *point01) // 200
}
函数传入的是指针参数(内存地址),因此在函数内的修改是在内存地址上的修改,在函数执行后还会保留结果。
指针与切片
切片与指针一样是引用类型。通过一个函数改变一个数组的值,可以将该数组的切片当作参数传给函数,也可以将这个数组的指针当作参数传给函数。
// 使用切片
func changeSlice(value []int) {
value[0] = 200
}
// 使用数组指针
func changeArray(value *[3]int) {
(*value)[0] = 200
}
func arrayPoint() {
test01 := [3]int{10, 20, 30}
changeSlice(test01[:])
fmt.Println(test01) // [200 20 30]
test02 := [3]int{100, 200, 300}
changeArray(&test02)
fmt.Println(test02) // [200 200 300]
}
Go 中不支持指针运算
x := [...]int{20, 30, 40}
p := &x
p++ // error
结构体
结构体(struct) 是一种聚合的数据类型,是由零个或多个任意类型的值聚合成的实体。每个值称为结构体的成员。在 Go 中,没有像 C++ 中的 class 类的概念,只有 struct 结构体的概念,所以也没有继承。
结构体的声明
在 Go 语言 中使用下面的语法是对结构体的声明。
type struct_name struct {
attribute_name1 attribute_type
attribute_name2 attribute_type
...
}
定义一个Person 结构体。
type Person struct {
name string
age int
sex int
}
声明了一个结构体类型 Person ,有 name 、 age 和 sex 三个属性。
声明结构体时也可以不用声明一个新类型,这样的结构体类型称为 匿名结构体(Anonymous Structure) 。
var Person struct {
name string
// 相同类型的属性声明在同一行
age, sex int
}
创建命名的结构体
func createPerson() {
// 没有被显式初始化时,结构体的字段将会默认赋为相应类型的零值。
person_zero := Person{}
person_one := Person{
name: "小明",
age: 18,
sex: 1,
}
person_two := Person{
"小红",
17,
1,
}
fmt.Println(person_one)
fmt.Println(person_two)
}
创建匿名结构体
person_three := struct {
name string
age, sex int
}{
name: "小蓝",
age: 14,
sex: 0,
}
访问结构体的字段
点操作符 . 用于访问结构体的字段
fmt.Println(person_one.name, person_one.age, person_one.sex)
// 小明 18 1
当然也使用点操作符 . 可以用于对结构体的字段的赋值。
person_one.name = "小鹿"
指向结构体的指针
person_four := &Person{
"小黑",
17,
1,
}
fmt.Println((*person_four).name)
fmt.Println(person_four.name)
在上面的程序中, person_four 是一个指向结构体 Person 的指针,上面用 (*person_four).name 访问 person_four 的 name 字段,上面的 person_four.name 代替 (*person_four).name 的解引用访问。
匿名字段
在创建结构体时,字段可以只有类型没有字段名,这种字段称为 匿名字段(Anonymous Field) 。
type UniquePerson struct {
string
int
}
person_five := UniquePerson{
"hhhhh",
111,
}
fmt.Println("person_five", person_five)
结构体中虽然这两个字段没有字段名,但匿名字段的名称默认就是它的类型。所以上面的结构体 UniquePerson 有两个名为 string 和 int 的字段。
嵌套结构体
结构体的字段也可能是另外一个结构体,这样的结构体称为 嵌套结构体(Nested Structs)
type Author struct {
name string
age int
}
type Person01 struct {
name string
price int
author Author
}
结构体比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的,那样的话两个结构体将可以使用 == 或 != 运算符进行比较。可以通过==运算符或 DeeplyEqual()函数比较两个结构相同的类型并包含相同的字段值。因此下面两个比较的表达式是等价的:
person_01 := Person{
name: "abc",
age: 12,
sex: 0,
}
person_02 := Person{
name: "abc",
age: 12,
sex: 0,
}
fmt.Println(person_01 == person_02)
给结构体定义方法
与 JS 中 Class 不同的是,Go 中结构体无法定义方法。
type Person02 struct {
name string
age int
}
func (p Person02) speak() {
fmt.Println("name", p.name)
fmt.Println("age", p.age)
}
person_03 := Person02{
name: "奶油桃子",
age: 12,
}
person_03.speak()
// name 奶油桃子
// age 12
上面的程序中定义了一个与结构体 Person02 绑定的方法 speak() ,其中 speak 是方法名, (p Person02) 表示将此方法与 Person02 的实例绑定,这在 Go 语言中称为接收者,而 p 表示实例本身。
函数
函数的声明
func function_name(params_list) (return_list) {
//函数体
}
函数声明使用 func 关键词,后面依次接 function_name(函数名) , params_list(参数列表) , return_list(返回值列表) 以及 函数体 。
无参数列表和返回值
func test01() {
fmt.Println("test")
}
函数返回一个无名变量,返回值列表的括号省略
func sum(x int, y int) int {
return x + y
}
参数的类型一致,只在最后一个参数后添加该类型
func sub(x , y int) int {
return x - y
}
可变参数
多个类型一致的参数
参数中加 ... 用来接收传入的参数。如果该函数下有其他类型的参数,这些其他参数必须放在参数列表的前面,...必须放在最后。这一点和 JS 的 args 还是有一些类似的。
func sum(x int, y int, args ...int) int {
sum := x + y
for _, item := range args {
sum += item
}
return sum
}
func main() {
fmt.Println(sum(10, 20, 1, 2, 3, 4, 5, 6)) // 51
}
多个类型不一致的参数
如果传多个参数的类型都不一样,可以指定类型为 ...interface{} ,然后再遍历。
func PrintType(args ...interface{}) {
for _, arg := range args {
switch arg.(type) {
case int:
fmt.Println(arg, "type is int.")
case string:
fmt.Println(arg, "type is string.")
case float64:
fmt.Println(arg, "type is float64.")
default:
fmt.Println(arg, "is an unknown type.")
}
}
}
func main() {
PrintType(12, 1.677, "哈哈哈哈")
}
匿名函数
没有名字的函数叫 匿名函数 ,只有函数逻辑体,而没有函数名。
func (params_list) (return_list) {
body
}
内部方法与外部方法
在 Go 语言中,函数名通过首字母大小写实现控制对方法的访问权限。
-
当方法的首字母为 小写 时,这个方法是 Private ,其他包是无法访问的。
-
当方法的首字母为 大写 时,这个方法对于 所有包 都是 Public ,其他包可以随意调用。
包
包(package) 用于组织 Go 源代码,提供了更好的可重用性与可读性。Go 语言有超过 100 个的标准包,这些库为大多数的程序提供了必要的基础组件。
main
包含一个 main() 函数,是程序运行的入口。
package packagename 代码指定了某源文件属于某个包。放在每一个源文件的第一行。
package main
import "fmt"
func main() {
fmt.Println("Go")
}
创建包
创建自定义的 dog 包,按照 Go 的惯例,应该用包名命名该文件夹。所以应当先创建一个 dog 文件夹,位于该目录下创建一个 dog.go 源文件,函数名的首字母要大写。
package dog
import "fmt"
type Dog struct {
color string
name string
sex int
}
func Adog() {
dog01 := Dog{
"黑色",
"桃桃",
1,
}
fmt.Print("dog\n", dog01)
}
导入包
使用包之前我们需要导入包,在 GoLand 中会帮你自动导入所需要的包。导入包的语法为 import path ,其中 path 可以是相对于工作区文件夹的相对路径,也可以是绝对路径。
包的初始化
每个包都允许有一个或多个 init 函数, init 函数不应该有任何返回值类型和参数,在代码中也不能显式调用它,当这个包被导入时,就会执行这个包的 init 函数,做初始化任务, init 函数优先于 main 函数执行。该函数形式如下:
func init() {
}
包的初始化顺序:首先初始化 包级别(Package Level) 的变量,紧接着调用 init 函数。包可以有多个 init 函数(在一个文件或分布于多个文件中),它们按照编译器解析它们的顺序进行调用。如果一个包导入了另一个包,会先初始化被导入的包。尽管一个包可能会被导入多次,但是它只会被初始化一次。
包的匿名导入
导入一个没有使用的包编译会报错。但有时候我们只是想执行包里的 init 函数来执行一些初始化任务的话应该怎么办呢?使用匿名导入的方法,使用 空白标识符(Blank Identifier) :
import _ "fmt"
由于导入时会执行该包里的 init 函数,所以编译仍会将此包编译到可执行文件中。
参考文章:go-edu.cn/