go进阶学习
go语言进阶学习
值拷贝和地址拷贝分析:
🔒 不会影响源数据(拷贝副本):
- 基本类型:
int,float64,string,bool,rune等 - 数组:
[n]int,[3]string等 - 结构体(仅包含上述值类型字段时)
✅ 赋值或传参时,会拷贝完整的数据,修改副本不会影响原数据。
🔄 会影响源数据(共享底层数据 / 传递引用):
- 指针:
*int,*struct等 - 切片:
[]int,[]string等 - 映射:
map[string]int等 - 通道:
chan int等
结构体struct注意事项:
1,结构体实例给实例赋值,是值传递
2,结构体序列化为json格式
//每个结构体的sturct属性都能定义一个tag标签
type Student struct {Name string `json:"name"`Age int `json:"age"`Score int `json:"score"`
}func main() {var student object.Student = object.Student{"魏正想", 18, 99}marshal, err := json.Marshal(student)if err != nil {fmt.Println("用户错误")}//将字符数切片转为string类型fmt.Println(string(marshal))
}
方法使用调用String()方法:
*Student类型实现了String方法,输出时输出该方法中的输出格式
type Student struct {Name string `json:"name"`Age int `json:"age"`Score int `json:"score"`
}func (student *Student) String() string {n := fmt.Sprintf("%v,%v,%v", student.Name, student.Age, student.Score)return n}func main() {var student Student = Student{"www", 19, 56}result := student.String()fmt.Println(result)fmt.Println(&student)
}
//输出格式!!!!!!
//www,19,56
方法与函数的区别:
1,调用方式不一样
函数调用: 函数名加实参列表
方法调用: 变量加方法名加实参列表
2,对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,防止亦然
3,对于方法比如结构体的方法,接收者为值类型时,可以直接用指针类型的变量调用方法,放过来同样也可以
工厂模式(相当于Java的构造函数):
省略哈哈哈哈哈!
go继承:
优势:
1,提高代码的复用性
2,代码的扩展性和维护性提高了
package mainimport "fmt"type student struct {name stringage intscore int
}func (stu *student) GetName() string {return stu.name
}type min struct {student
}
type max struct {student
}func main() {//继承练习//记得加= &min{},不然只是定义了类型,未分配内存var aaa *min = &min{}aaa.student.name = "魏正想"aaa.student.age = 20aaa.student.score = 99name := aaa.student.GetName()fmt.Println(name)//可以直接通过aaa这只name名字aaa.name = "lulu"getName := aaa.GetName()fmt.Println(getName)
}
注意点:
1,结构体可以使用嵌套结构体所有字段和方法,即首字母大写或者小写的字段,方法都可以使用
2,如果b继承a,访问name字段时,先看b中有没有name再看a中又没有name字段,如果都找不到就报错
3,当结构体有匿名结构体(继承的结构体)有相同的字段或者方法时,编译器采用就近访问原则访问。
4,如果一个结构体嵌套了一个有名的结构体,这种模式就是组合,如果是组合关系,那么在访问组合的结构体的字段或方法时,必须带上结构体的名字。
type student struct {name stringage int//如果一个结构体有int类型的匿名字段,就不能有第二个//如果需要多个int字段,则必须给int字段指定名字intscore int
}
go可以嵌入多个类型实现类似多继承的代码复用,本质是组合。
Java中有限制,只能实现单一继承。
go中为了代码的简洁性,尽量避免使用多重继承
go接口:
package mainimport "fmt"type animal interface {eat()sleep()
}type cat struct {
}func (ccc cat) eat() {fmt.Println("cat开始进食")
}
func (ccc cat) sleep() {}type dog struct {
}func (ddd dog) eat() {}func (ddd dog) sleep() {fmt.Println("dog开始睡觉")
}func having(animal2 animal) {animal2.eat()animal2.sleep()
}
func main() {var ccc catvar ddd doghaving(ccc)having(ddd)
}
注意:接口里的所有方法都没有方法体,也不能定义变量(Java中可以)
go接口中不需要显式的实现,只要一个变量,含有接口中的所有方法,变量就实现了这个接口
Java中需要显式实现,需要implement关键字来进行显示实现
go中接口默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil
空接口interface{}没有任何方法,所以所有类型都实现空接口,即我们可以把任何一个变量赋给空接口
go多态:
文件操作:
读取文件
package mainimport ("bufio""fmt""io""os"
)func main() {file, err := os.Open("1goAdvanced/file/fileReader/aaa.txt")if err != nil {fmt.Println("文件读取异常")}//每次读完要关闭文件,否则会导致内存泄漏defer file.Close()reader := bufio.NewReader(file)for {readString, err := reader.ReadString('\n')if err == io.EOF {break}fmt.Println(readString)}fmt.Println("文件读取结束")
}
go一次性读取文件:
只适合文件比较小的读取,文件太大性能会非常差
ioutil.ReadFile(“文件路径”)
创建文件并写入内容:
func OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//创建文件并写入内容
filePath := "d:/abc.txt"
file1, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {fmt.Println("%v", err)
}
defer file1.Close()
str := "helloWorld\n"
writer := bufio.NewWriter(file1)
for i := 0; i < 5; i++ {writer.WriteString(str)
}
//将缓存写入文件中
writer.Flush()
copy(目标文件,源文件)拷贝文件
解析命令行参数
os.Args
flag包
//命令行 test.exe -u root -pwd 1234 -port localhost
var port int
var pwd string
//得到port后面的值,如果没有默认是3306
flag.IntVar(&port, "port", 3306, "端口默认值是3306")
flag.StringVar(&pwd, "pwd", "", "端口")
json概述:
JSON是一种轻量级数据交换格式,特别有利于网络传输
任何数据类型都可以用json来表示
interface{}代表任意类型
序列化Marshal:
json.Marshal( args interface{})
//注意事项:
结构体序列化时,要保证内部属性都是大写字母开头,因为json包跨包序列化,需要结构体内部属性都是可以外部访问的,并且传指针更轻量化一些
type teacher struct {Name stringAge intJob string
}func main() {var tea teacher = teacher{Name: "mmm",Age: 11,Job: "ninin",}marshal, _ := json.Marshal(tea)fmt.Println(string(marshal))}
反序列化Unmarshal:
func Unmarshal(data []byte, v interface{}) error
注意:对于map这种反序列化的结果赋值给一个map实例时,这个map实例不用事先make,因为Unmarshal底层会进行make操作
goroutine(协程):
查看系统cpu(NumCPU):
func main() {cpu := runtime.NumCPU()fmt.Println(cpu)
}
进程和线程的说明:
1,进程是程序在操作系统中一次执行过程,是系统进行资源分配和调度的基本单位
2,线程是进程的一个执行实例,是程序执行的最小单元,是比进程更小的能独立运行的基本单位
3,一个进程可以创建和销毁多个线程,同时一个进程中多个线程可以并发执行
4,一个程序至少有一个进程,一个进程中至少有一个线程
并发和并行:
并发:多任务在一个cpu上运行
并行:多任务在多个cpu上运行
协程基本概念:
1,主线程是一个物理线程,直接作用在cpu上的,是重量级的,非常消耗cpu资源。
2,协程从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对较小
3,golang的协程机制是重要的特点,可以轻松的开启上万个协程。其他编程语言的开发机制是一般基于线程的,开启过多的线程,资源消耗大,这里就突显golang在并发上的优势了。
了解go中MPG模式基本介绍。
简介:原来M0主线程正在执行g0协程,另外三个协程在队列等待
如果g0协程阻塞,比如说io操作,这是会创建M1主线程也可能从已有的线程池中取出M1,并将等待的3个协程挂到M1下开始执行,M0主线程仍然执行io操作,不会让队列阻塞。
go协程案例:
// 计算1到20的每个数的阶乘的和
// 定义全局变量
var (nums map[int]int = make(map[int]int, 10)lock = sync.Mutex{}
)func test(num int) {var temp int = 1for i := 1; i <= num; i++ {temp *= i}lock.Lock()nums[num] = templock.Unlock()
}func main() {//fmt.Println()for i := 1; i <= 20; i++ {//定义协程go test(i)}time.Sleep(time.Second * 5)lock.Lock()for _, num := range nums {fmt.Println(num)}lock.Unlock()
}
channel基本介绍:
特点:
channel本质就是一个数据结构队列
数据是先进先出
channel是线程安全的,多个协程访问时,不用加锁
channel有类型的,一个string的channel只能存放string类型数据
依次取除channel中的数据:
channel两种读取错误分析:
注意:在遍历是len(cha)是动态变化的,每取出一次,channel的长度就会缩短,不能用channel作为限制条件
方式一:❌
var cha chan int// channel初步练习
func main() {//查看运行系统中有几核cpucpu := runtime.NumCPU()fmt.Println(cpu)cha = make(chan int, 3)cha <- 22cha <- 99cha <- 66var temp intfor i := 0; i < len(cha); i++ {temp = <-chafmt.Printf("从管道中提取出数据%v", temp)fmt.Println()}
}
方式二:✔
var cha chan int// channel初步练习
func main() {//查看运行系统中有几核cpucpu := runtime.NumCPU()fmt.Println(cpu)//cha = make(chan int, 3)cha <- 22cha <- 99cha <- 66var length int = len(cha)var temp intfor i := 0; i < length; i++ {temp = <-chafmt.Printf("从管道中提取出数据%v", temp)fmt.Println()}
}
管道使用注意事项:
channel中只能存放指定数据类型
数据放满后就不能在存放,如果取出数据,可以继续放入
在没有使用协程的情况下,如果管道数据取完了, 再取,会报dead lock
对于结构体,在编译时默认是interface
所以需要进行类型断言
类型断言与强制类型转换:
注意:类型转换是“换汤不换药”,给数据换个类型外衣;类型断言是“打开盲盒”,看看接口里到底装了啥。
- 当你有一个 接口类型的变量,但你想知道它具体是什么类型,并取出该实际值
- 常用于处理 多种可能的类型,比如空接口 interface{} 包含不同类型数据时
类型断言:
类型断言 是用于 接口类型(interface) 的操作,目的是 从接口变量中提取出它实际存储的具体类型值,或者判断该接口是否实现了某个具体类型或接口。
换句话说:类型断言让你可以检查接口值背后到底是什么具体类型,并获取该值。
//可以接收任何的数据类型
var inter chan interface{} = make(chan interface{}, 3)
inter <- 23.57
inter <- 2222222222222222
type dog struct {name string
}
var doo = dog{name: "mmmmmmmm",
}
inter <- doo
//取出数据
//前一个丢弃
<-inter
bbb := <-inter
fmt.Println(bbb)
sss := <-inter
fmt.Println(sss)
//详细获取到name需要进行类型断言
result := sss.(dog)
fmt.Println(result.name)
管道关闭+channel遍历:
管道遍历,前一定要关闭管道!!!!
var inter chan interface{} = make(chan interface{}, 3)
inter <- 23.57
inter <- 222222
type dog struct {name string
}
var doo = dog{name: "mmmmmmmm",
}
inter <- doo
//管道遍历,前一定要关闭管道
close(inter)
for v := range inter {fmt.Println(v)
}
