注意点
包和目录的关系
go 用文件目录来管理包,一个文件目录可以有多个.go 文件,要求目录内所有的文件的包名要一致 默认包名和目录名称保持一致,不强制要求
用包来管理元素的可见行,大写开头的元素包外也可见,小写开头的元素只能在包内使用
目录 p1 内的俩个go 文件
第一个.go 文件
package p1
import "fmt"
func F1() {
F3()
f4()
fmt.Println("F1")
}
func f2() {
fmt.Println("f2")
}
第二个 .go 文件
package p1
import "fmt"
func F3() {
F1()
f2()
fmt.Println("F3")
}
func f4() {
fmt.Println("f4")
}
main .go 文件
package main
//引用包
import "awesomeProject/p1"
func main() {
p1.F1()
//私有成员包外不可见
//p1.f2()
p1.F3()
//私有成员包外不可见
//p1.f4()
}
引入包的注意点
-
包别名
package main //引用包别名 import p2 "awesomeProject/p1" func main() { p2.F1() //私有成员包外不可见 //p2.f2() p2.F3() //私有成员包外不可见 //p2.f4() }
-
省略包名直接使用
通过 . 号 来省略包名
package main //引用包 import ."awesomeProject/p1" func main() { F1() //私有成员包外不可见 //f2() F3() //私有成员包外不可见 //f4() }
-
不使用包但是使用包里的初始化方法
package p1 import "fmt" var I = 10 //包的初始化 //在包被引入的时候执行 func init() { fmt.Printf("初始化包,%d\n", I) } func F1() { fmt.Println("F1") }
package main //引用包 import ( //通过 _ 引入的包表示只执行包的初始化方法 _ "awesomeProject/p1" ) func main() { //不能使用包内成员 //p1.F1() }
值类型和引用类型
值类型包括基本数据类型,int,float,bool,string,以及数组和结构体(struct)。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于栈上. 当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝,可以通过 &i 获取变量 i 的内存地址。此时如果修改某个变量的值,不会影响另一个
引用类型包括指针,slice切片,map,chan,interface,func。变量直接存放的就是一个内存地址值,这个地址值指向的空间存的才是值。所以修改其中一个,另外一个也会修改(同一个内存地址)。引用类型必须申请内存才可以使用,make()是给引用类型申请内存空间
数组是值类型 ,切片是引用类型
数组是一组同类型数据的集合,它是值类型,通过从0开始的下标索引访问元素值。在初始化后长度是固定的,无法修改其长度。当作为方法的参数传入时将复制一份数组而不是引用同一指针。数组的长度也是其类型的一部分,通过内置函数len(array)获取其长度
//数组 值类型
{
var i = [3]int{1, 2, 3}
//[...] 表示其长度有元素个数决定 var i = [...]int{1, 2, 3}
var j = i
j[0] = 10
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, &i)
//i type [3]int, value [1 2 3] ,addr 0xc0000ae078
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, &j)
//j type [3]int, value [10 2 3] ,addr 0xc0000ae090
}
与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。 切片中有两个概念:一是len长度,二是cap容量,长度是指已经被赋过值的最大下标+1,可通过内置函数len()获得。 容量是指切片目前可容纳的最多元素个数,可通过内置函数cap()获得。切片是引用类型, 因此在当传递切片时将引用同一指针,修改值将会影响其他的对象。 切片可以通过数组来初始化,也可以通过内置函数make()初始化.初始化时len=cap, 在追加元素时如果容量cap不足时将按len的2倍扩容
//切片 引用类型
{
var i = []int{1, 2, 3}
var j = i
j[0] = 10
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [10 2 3] ,addr 0xc00000c168
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, j)
//j type []int, value [10 2 3] ,addr 0xc00000c168
}
切片切出来的部分和原来的切片是共享一个堆内存对象
{
var i = []int{1, 2, 3}
//切片切出来的部分和原来的切片是共享一个堆内存对象
var j = i[0:2]
j[0] = 100
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [100 2 3] ,addr 0xc00000c168
fmt.Printf("j type %T, value %v ,addr %p\n", j, j, j)
//j type []int, value [100 2] ,addr 0xc00000c168
}
空切片
{
var i = []int{1, 2, 3}
var j = i[0:0]
}
完整的切片和原来切片一样
{
var i = []int{1, 2, 3}
var l = i[:]
}
用 copy 在原有切片的基础上生成一个全新的切片
{
var i = []int{1, 2, 3}
var l = make([]int, 10)
copy(l, i)
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, i)
//i type []int, value [1 2 3] ,addr 0xc00000c168
fmt.Printf("i type %T, value %v ,addr %p\n", l, l, l)
//i type []int, value [1 2 3 0 0 0 0 0 0 0] ,addr 0xc000014370
}
结构体
结构体定义
结构体是值类型,类似与Java 的 Class ,但是 Go 的结构体内不能定义方法实现,静态成员 结构体没有继承的概念,使用组合代替继承
定义父类
type Animal struct {
name string
}
父类构造函数
func New(name string) *Animal {
return &Animal{name: name}
}
父类的实例方法
//(this *Animal) 通过指针确保方法内的 this 和调用方法的变量指向的是同一个实例
func (this *Animal) SetName(name string) {
this.name = name
}
//(this Animal) 读取取 copy 的值对象
func (this Animal) GetName() string {
return this.name
}
子类定义 通过嵌套继承父类
//子类
type Bird struct {
flyDistance int
//匿名父类 等价于Animal Animal
Animal
}
结构体初始化及使用
初始化方法1
var i Animal = Animal{name: ""}
i.SetName("name1")
fmt.Printf("i type %T, value %v ,addr %p\n", i, i, &i)
//i type main.Animal, value {name1} ,addr 0xc00004e250
初始化方法2 对结构体进行&取地址操作时,视为对该类型进行一次 new 的实例化操作
var l *Animal = &Animal{name: ""}
i.SetName("name2")
fmt.Printf("l type %T, value %v ,addr %p\n", l, l, l)
//l type *main.Animal, value &{name2} ,addr 0xc00004e280
初始化方法3
//创建指针类型的结构体
var m *Animal = new(Animal)
m.SetName("name3")
fmt.Printf("m type %T, value %v ,addr %p\n", m, m, m)
//m type *main.Animal, value &{name3} ,addr 0xc00004e2a0
子类初始化
var iSon = Bird{maxFlyDistance: 1000, Animal: Animal{name: ""}}
iSon.SetName("bird1")
iSon.name = "bird_1"
iSon.maxFlyDistance = 1000
fmt.Printf("iSon type %T, value %v ,addr %p\n", iSon, iSon, &iSon)
//iSon type main.Bird, value {1000 {bird_1}} ,addr 0xc000004078
接口
接口是引用类型,接口可以通过组合的形式形成继承接口
接口定义
接口内定义方法的声明,接口可以通过组合的形式达到继承接口的效果
type Base interface {
eat()
}
type Animal interface {
walk()
Base
}
接口的实现
接口有多个实现的实例,多态的效果
type Dog struct {
name string
}
type Pig struct {
name string
}
func (this Dog) eat() {
fmt.Printf("dog name %s eat\n", this.name)
}
func (this Pig) eat() {
fmt.Printf("pig name %s eat\n", this.name)
}
func (this Dog) walk() {
fmt.Printf("dog name %s walk\n", this.name)
}
func (this Pig) walk() {
fmt.Printf("pig name %s walk\n", this.name)
}
接口的使用
func bar(i Animal) {
i.walk()
i.eat()
}
func main() {
var i Animal = Dog{name: "dog1"}
var j Animal = Pig{name: "pig1"}
bar(i)
//dog name dog1 walk
//dog name dog1 eat
bar(j)
//pig name pig1 walk
//pig name pig1 eat
}
反射
反射可以反射基础类型和和明确类型
反射是指在程序运行期对程序本身进行访问和修改的能力。程序在编译时,变量被转换为内存地址,变量名不会被编译器写入到可执行部分。在运行程序时,程序无法获取自身的信息。
支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
Go程序在运行期使用reflect包访问程序的反射信息。
反射是由 reflect 包提供的。它定义了两个重要的类型,Type 和 Value。一个 Type 表示一个Go类型。它是一个接口
反射字段,方法,tag 及基础类型
需要反射的结构体 结构体可以加标签 相当于是 Java 注解
type str struct {
id int `dis:"id" value:"1"`
name string `dis:"name" value:"name1"`
}
func (this str) F(x string) string {
return x
}
反射字段
func GetFields(ty reflect.Type, val reflect.Value) {
for i := 0; i < ty.NumField(); i++ {
var field reflect.StructField = ty.Field(i)
var value = val.Field(i)
fmt.Printf("field name %s, type %v , value %v\n", field.Name, field.Type, value)
}
}
反射公有方法
func GetMethods(ty reflect.Type, val reflect.Value) {
for i := 0; i < ty.NumMethod(); i++ {
var field reflect.Method = ty.Method(i)
var value = val.Method(i)
fmt.Printf("field name %s, type %v , value %v\n", field.Name, field.Type, value)
}
}
反射 tag
func GetTags(ty reflect.Type) {
for i := 0; i < ty.NumField(); i++ {
var value1 = ty.Field(i).Tag.Get("dis")
var value2 = ty.Field(i).Tag.Get("value")
fmt.Printf("field name %s, tag1 %s , tag2 %s\n", ty.Field(i).Name, value1, value2)
}
}
func main() {
//基础类型的反射
var i = 10
var ty1 reflect.Type = reflect.TypeOf(i)
var val1 reflect.Value = reflect.ValueOf(i)
fmt.Printf("type name %s, value %v\n", ty1.Name(), val1)
//type name int, value 10
//结构体反射
var j = str{id: 10, name: "bar"}
var ty2 = reflect.TypeOf(j)
var val2 = reflect.ValueOf(j)
fmt.Printf("type name %s, value %v\n", ty2.Name(), val2)
//type name str, value {10 bar}
GetFields(ty2, val2)
//field name id, type int , value 10
//field name name, type string , value bar
GetMethods(ty2, val2)
//method name F, type func(main.str, string) string , value 0xde99e0
GetTags(ty2)
//tag name id, tag1 id , tag2 1
//tag name name, tag1 name , tag2 name1
}
GOPATH 工作方式
在 GOPATH 指定的工作目录下,代码总是会保存在 $GOPATH/src 目录下。在工程经过 go build、go install 或 go get 等指令后,会将产生的二进制可执行文件放在 $GOPATH/bin 目录下,生成的中间缓存文件会被保存在 $GOPATH/pkg 下。
GOPATH 有两种
- 全局的GOPATH,来源于系统环境变量中的 GOPATH
- 项目的GOPATH
指定了GOPATH 也就指定了包的搜索路径 ,项目的GOPATH 随项目设定 GOPATH 定义了绝对的文件搜索路径
GOPATH 项目演示
-
指定工作目录 F:/GoWorkPath
-
工作目录下建立src 目录
-
在 src 下建立目录 libs1,libs2,main 目录
libs1 目录 .go 代码
package libs1 import "fmt" func F() { fmt.Println("libs1 func F Z 执行") }
libs2 目录 .go 代码
package libs2 import "fmt" func F() { fmt.Println("libs2 func F Z 执行") }
main 目录 .go 代码 import 根据指定的 项目 GOPATH 可以直接导入 src 下的 包,
package main import ( //不需要指定路径 "libs1" //如果没有指定项目 GOPATH ,需要根据当前的 .go 文件的目录指定相对的.go 包文件路径 //"../libs1" "libs2" "time" ) func main() { libs1.F() libs2.F() time.Sleep(5 * time.Second) }
GO MOD 工作方式
Modules是相关Go包的集合,是源代码交换和版本控制的单元。go命令直接支持使用Modules,包括记录和解析对其他模块的依赖性。Modules替换旧的基于GOPATH的方法,来指定使用哪些源文件。
Modules和传统的GOPATH不同,不需要包含例如src,bin这样的子目录,一个源代码目录甚至是空目录都可以作为Modules,只要其中包含有go.mod文件.
父目录里有.mod 文件,父目录里的文件及父目录的子目录里的文件都受mod管理, 目前的推荐在 MOD 模式下工作.
GO MOD 项目演示
-
GO 版本1.11以上
-
设置GO111MODULE GO111MODULE=off,go命令行将不会支持module功能,寻找依赖包的方式将会沿用旧版本那种 通过vendor目录或者GOPATH模式来查找。
GO111MODULE=on,go命令行会使用modules,而一点也不会去GOPATH目录下查找。
GO111MODULE=auto,默认值,go命令行将会根据当前目录来决定是否启用module功能。这种情况下可以分为两种情形:
- 当前目录在GOPATH/src之外且该目录包含go.mod文件
- 当前文件在包含go.mod文件的目录下面。
-
在GOPATH/src 目录之外新建工程目录
-
在此目录下新建俩个目录 project1,project2 分别在这俩个目录内
-
执行 go mod int project1, go mod int project2在俩个目录生成 go.mod 文件 这是一个关键文件,之后的包的管理都是通过这个文件管理
-
在目录project2内新建目录libs 目录,在此目录新建f.go 文件
package libs import "fmt" func F() { fmt.Println("project2 libs F 执行") }
-
在目录project1内修改 go.mod 文件
project1 这个模块需要引用本地的其他自定义模块 project2 需要指定相对当前go.mode 文件的路径 replace project2 => ../project2 require project2 v0.0.0
module project1 replace project2 => ../project2 require project2 v0.0.0 //引入第三模块的依赖 require ( github.com/sirupsen/logrus v1.8.1 // indirect golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect ) go 1.18
-
在在目录project1 内新建目录libs 在此目录新建 f.go 文件
package libs import "fmt" func F() { fmt.Println("project1 libs F 执行") }
-
在目录project1 内新建目录mainX 在此目录新建 main.go 文件
package mainX import ( "fmt" log "github.com/sirupsen/lours" p1 "project1/libs" p2 "project2/libs" "time" ) func main() { fmt.Println("main") p1.F() p2.F() log.Info("我是一条日志") time.Sleep(5 * time.Second) }
-
在 main.go文件所在目录执行 go mod tidy 会清理无用的模块引用 go build 下载引用的模块及执行编译生成 mainX.exe 文件 执行文件 ./mainX.exe 结果
mainX project1 libs F 执行 project2 libs F 执行 time="2022-04-20T11:33:05+08:00" level=info msg="我是一条日志"
-
本次笔记记录时间 2022-04-20,疫情期间,下次开始学习框架
mvc 数据库
mvc 数据库 网络通讯