旅游网站建设方案2019,wordpress安装创建数据库,创世网站建设公司,网页设计与制作个人网站模板面向对象 Golang 也支持面向对象编程(OOP)#xff0c;但是和传统的面向对象编程有区别#xff0c;并不是纯粹的面向对象语言。 Golang 没有类(class)#xff0c;Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位#xff0c;Golang 是基于 struct 来实现 OOP…面向对象 Golang 也支持面向对象编程(OOP)但是和传统的面向对象编程有区别并不是纯粹的面向对象语言。 Golang 没有类(class)Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位Golang 是基于 struct 来实现 OOP 特性的去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等 Golang 仍然有面向对象编程的继承封装和多态的特性只是实现的方式和其它 OOP 语言不一样比如继承 Golang 没有 extends 关键字继承是通过匿名字段来实现。 Golang 面向对象(OOP)很优雅OOP 本身就是语言类型系统(type system)的一部分通过接口(interface)关联耦合性低也非常灵活。
一、基础知识
数据类型 ## golang字符类型
字符类型的本质是一个整数占8个字节Go 的字符串是由字节组成的根据utf-8编码
字符型 存储到 计算机中需要将字符对应的码值整数找出来
存储字符—对应码值----二进制–存储
读取二进制---- 码值 ---- 字符 -- 读取
字符和码值的对应关系是通过字符编码表决定的(是规定好)## golang字符串类型
两种表现形式
(1) 双引号, 会识别转义字符
(2) 反引号以字符串的原生形式输出包括换行和特殊字符可以实现防止攻击、输出源代码等效果溢出问题 a : int8(127)b : int8(1)fmt.Println(a b) // 输出-128不会报错a : uint8(255)b : uint8(1)fmt.Println(a b) //输出0不会报错rune 类型相当int32由于golang中的字符串底层实现是通过byte数组的中文字符在unicode下占2个字节在utf-8编码下占3个字节
byte 等同于int8常用来处理ascii字符rune 等同于int32,常用来处理unicode或utf-8字符
数组和切片
数组
数组的地址可以通过数组名来获取 intArr数组的第一个元素的地址就是数组的首地址数组的各个元素的地址间隔是依据数组的类型决定比如 int64 - 8 int32-4…
切片
slice 底层数据结构是由一个 array 指针指向底层数组len 表示切片长度cap 表示切片容量
当扩容时
假如 slice 容量够用则追加新元素进去slice.len返回原来的 slice。当原容量不够则 slice 先扩容扩容之后 slice 得到新的 slice将元素追加进新的 sliceslice.len返回新的 slice。
扩容规则当切片比较小时容量小于 1024则采用较大的扩容倍速进行扩容新的扩容会是原来的 2 倍避免频繁扩容从而减少内存分配的次数和数据拷贝的代价。当切片较大的时原来的 slice 的容量大于或者等于 1024采用较小的扩容倍速新的扩容将扩大大于或者等于原来 1.25 倍主要避免空间浪费和切片的区别
1数组是定长访问和复制不能超过数组定义的长度否则就会下标越界切片长度和容量可以自动扩容2数组是值类型切片是引用类型每个切片都引用了一个底层数组切片本身不能存储任何数据都是这底层数组存储数据所以修改切片的时候修改的是底层数组中的数据。切片一旦扩容指向一个新的底层数组内存地址也就随之改变
Channel
go中的channel是一个队列遵循先进先出的原则负责协程之间的通信channel 是 goroutine 之间数据通信桥梁而且是线程安全的写入读出数据都会加锁。
三种类型只读 channel、只写 channel意义在于在参数传递时候指明管道可读还是可写即使当前管道是可读写的、可读可写 channel
channel 中只能存放指定的数据类型
channle 的数据放满后就不能再放入了
在没有使用协程的情况下如果 channel 数据取完了再取就会报 dead lock
goroutine 中使用 recover解决协程中出现 panic导致程序崩溃问题应用场景
停止信号监听定时任务生产方和消费方解耦控制并发数
底层原理 有缓冲的channel使用ring buffer环形缓冲区来缓存写入的数据本质是循环数组为啥用循环数组普通数组容量固定、更适合指定的空间且弹出元素时元素需要全部前移
流程
## 写数据
如果channel的读等队列存在接受者goroutine
将数据直接发送给第一个等待的goroutine,唤醒接收的goroutine
如果channel的读等队列不存在接受者goroutine如果循环数组的buf未满那么将数据发送到循环数组的队尾如果循环数组的buf已满将当前的goroutine加入写等待对列并挂起等待唤醒接收
## 读数据
如果channel的写等待队列存在发送者goroutine如果是无缓冲channel直接从第一个发送者goroutine那里把数据拷贝给接收变量唤醒发送的gorontine如果是有缓冲channel(已满),将循环数组buf的队首元素拷贝给接受变量将第一个发送者goroutine的数据拷贝到循环数组队尾唤醒发送端goroutine如果channel的写等待队列不存在发送者goroutine如果循环数组buf非空将循环数据buf的队首元素拷贝给接受变量如果循环数组buf为空这个时候就会走阻塞接收的流程将当前goroutine加入读等队列并挂起等待唤醒## 相比较共享内存共享内存访问需要加锁若持锁失败要么忙等重试要么待会儿再来。降低耦合channel以消息传递通信消息发出后就不用管了除非它希望得到回馈完全异步。
Map
原理底层使用 hash table每个 map 的底层结构是 hmap是有若干个结构为 bmap链表 的 bucket 组成的数组。用链表来解决冲突 出现冲突时不是每一个 key 都申请一个结构通过链表串起来而是以 bmap 为最小粒度挂载一个 bmap 可以放 8 个 kv。在哈希函数的选择上会在程序启动时检测 cpu 是否支持 aes如果支持则使用 aes hash否则使用 memhash。
key 可以是很多种类型比如 bool, 数字string, 指针, channel ,接口, 结构体, 数组slice map 还有 function 不可以因为这几个没法用 来判断
声明是不会分配内存的初始化需要 make 分配内存后才能赋值和使用
map对象不是线程安全的并发读写的时候运行时会有检查遇到并发问题就会导致panic
## 内存回收
1. go 底层map 是由若干个bmap桶构成的桶只会扩容不会缩容 所以 map中占用的内存不会被释放
以上只针对值类型的数据结构 例如基本类型 int string slice struct 等
2. 如果key为 指针变量 删除后这个指针变量内存不会释放但是这个指针指向的对象引用计数会 -1 如果引用计数为0 在gc的时候就会被释放## 元素有序性
map 因扩张⽽重新哈希时各键值项存储位置都可能会发生改变顺序自然也没法保证了所以官方避免大家依赖顺序直接打乱处理每次遍历得到的输出 可能不一样。
for range map 在开始处理循环逻辑的时候就做了随机播种要想有序遍历可以先将 key 进行排序然后根据 key 值遍历## 线程安全
map对象不是线程安全的并发读写的时候运行时会有检查遇到并发问题就会导致panic
解决方法使用sync.Map、使用读写锁结构体
type Person struct {Name string json:name-fieldAge int
}结构体指针访问字段的标准方式应该是(*结构体指针).字段名 但 go 做了一个简化也支持 结构体指针.字段名, 更加符合程序员使用的习惯go 编译器底层 对 person.Name 做了转化 (*person).Name。结构体的所有字段在内存中是连续的结构体进行 type 重新定义(相当于取别名)Golang 认为是新的数据类型但是相互间可以强转和其它类型进行转换时需要有完全相同的字段(名字、个数和类型struct 的每个字段上可以写上一个 tag, 该 tag 可以通过反射机制获取常见的使用场景就是序 列化和反序列化。
函数与方法
//函数
func getArea(R int) float64 {return math.Pi * math.Pow(R, 2)
}
//方法
func (c Circle)getArea() float64 {return math.Pi * math.Pow(c.R, 2)
}方法的调用和传参机制和函数基本一样不一样的地方是方法调用时会将调用方法的变量当做实参也传递给方法体现了封装性。函数则是无状态的代码块。
Go 的函数参数传递都是值传递调用函数时将实际参数复制一份传递到函数中这样在函数中如果对参数进行修改将不会影响到实际参数。
对象
make和new
1作用变量类型不同new给string,int和数组分配内存make给切片mapchannel分配内存2返回类型不一样new返回指向变量的指针make返回变量本身3new 分配的空间被清零。make 分配空间后会进行初始化 继承
type Person struct {id intname stringage int
}type Student struct {Personid intscore intclassName string
}使用匿名属性来实现继承即将父类作为子类的匿名属性如果父类和子类中有重复字段则优先使用子类自身的属性方法的重写方法名参数返回值类型都必须一样此时调用方法绑定的对象不在时父类而是子类本身
接口
空接口
// fmt包中的方法 Println底层
func Println(a ...interface{}) (n int, err error) {return Fprintln(os.Stdout, a...)
}
// 接纳任意对象
var i interface{} 45
i[...]int{1,2,3}可以接纳任意对象类似java中的Object
接口
可以定义一些通用的方法将被继承和实现的接口以匿名属性传入即可但不必将所有的方法都实现
type annimal interface {eat()sleep()run()
}
type cat interface {annimalClimb()
}多态
可以在调用方法时会因传入对象的不同而得到不同的效果
// 使用 对象.(指定的类型) 判断改对象是否时指定的类型
if data,ok :v.(cat);ok{data.eat()fmt.Println(this is HelloKitty : )}实现接口中的方法可以通过指针和结构体绑定
type animal interface {eat()
}
type Dog struct {Name stringAge int
}
//func (d Dog) eat() { 结构体绑定
//}
func (d *Dog) eat() { 指针绑定
}func main() {var a animaldPoint : Dog{Name: susan,Age: 12,}dStruct : Dog{Name: susan,Age: 12,}a dPoint// 使用指针接收者实现接口不能存结构体类型变量// a dStruct
}区别使用值接受者实现接口结构体类型和结构体指针类型的变量都能存指针接收者实现接口只能存指针类型的变量
异常
编译时异常在编译时抛出的异常编译不通过语法使用错误符号填写错误等等。。。运行时异常在程序运行时抛出的异常这个才是我们将要说的程序运行时有很多状况发生例如让用户输入一个数字可用户偏偏输入一个字符串导致的异常数组的下标越界空指针等等。。。。
编译时异常很容易找到而运行时异常不容易提前发现通过if err ! nil判断但是依然会漏掉很多异常因此我们需要在运行过程中动态的捕获异常
defer和recover
defer延时执行即在方法执行结束出现异常而结束或正常结束时执行 recover恢复的意思如果是异常结束程序不会中断返回异常信息可以根据异常来做出相应的处理 recover必须放在defer的函数中才能生效
func test(a int, b int) int {defer func() {err : recover()fmt.Println(err:,err)}()a b / areturn a
}
func main() {i : test(0, 1)fmt.Println(main方法正常结束,i)
}//结果
err: runtime error: integer divide by zero
main方法正常结束 0手动抛出异常——panic
有些异常是不应该恢复的应该抛出异常可以让这个异常一层层的返回给调用方的程序使其不能继续执行从而起到保护后面业务的目的
func test(a int) int {i:100 - aif i0{panic(errors.New(账户金额不足))}fmt.Println(账户扣款)return i
}