从本科学过C/C++和java,但是实际项目中用的多的是python. 因此python深刻影响了我的编程思维, 从python转到Go, 让我总结了一些学习笔记.
如果你在python之前没有C或JAVA等静态语言的代码量积累, 接触go之后, 编程习惯上最大的各种不同基本都可以归根于动态语言与静态语言的区别.
简单来说,两门语言各自的编译解释的方式不同,造成了编程时思维的不同.python是一行一行地编写,而go需要把当前的.go文件当做一个整体来编写.
可能体现在:
相比python更加严格一些.
开头提及,不能import没有用到的模块.import使用上也有小细节.
可以直接使用模块中的变量与方法
import . "fmt"
func main(){
Println("Hello")//而不是fmt.Println("Hello")
}
使用别名代替,如同python的import…as…
import f "fmt"
func main(){
f.Println("Hello")
}
仅执行到模块的init()文件,而不能使用模块的变量或方法.
import _ "fmt"
func main() {
fmt.Println("helo") // error
可以理解为,python文件当中常常需要做
if __name__=="__main__":
...
...
这样的判断,因为有些代码你希望它在当前模块被import时并不运行,只在自身作为mian程序运行时才执行.
显然使用if判断是python当中一个略显粗糙的设计,golang通过丰富import的用法完善这个功能.
var 变量名 [类型] [=] [初始值]
“:=” : 便捷写法, 完成类型声明与赋值.(因此要求变量既未被声明也未被赋值, 换言之, 这个变量在上文还没出现过)
下划线_: 不被使用的值
如python中,
for _ in this_list 完成遍历, 但不使用列表中的值
或
a, _, c, = this_func() // 不被使用的返回值
python已有约定俗成的用法,只是go把它列入了编程规范.
struct替代class, 因此没有构造函数和析构函数
接口interface替代继承关系
variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
func (this Type) func_type(){
}
不同于python或其他语言分为值传递和引用传递(其实也是因为python没有指针),Go所有函数参数在函数内部都是值传递,即把参数copy之后在函数内使用.形参与实参除了值相同,在内存中是两份变量存在.
因此想要在函数内部修改对象的话,就要使用指针作为参数.
eg:
type Student struct {
name string
age int
}
func change(s Student) {
s.name = "changed"
fmt.Println(s)
}
func main() {
s := Student{"chris", 23}
fmt.Println(s)
change(s)
fmt.Println(s)
}
输出:
{chris 23}
{changed 23}
{chris 23}
可以看到函数内部的修改无法改变外部值
当使用指针时
type Student struct {
name string
age int
}
func change(s *Student) {
s.name = "changed"
fmt.Println(s)
}
func main() {
s := Student{"chris", 23}
fmt.Println(s)
change(&s) //传入地址
fmt.Println(s)
}
输出:
{chris 23}
&{changed 23}
{changed 23}
可以改变对象的变量值
替代继承的用法,称为组合而非继承
type User struct {
Name string
Email string
}
type Admin struct {
User
Level string
}
func main() {
admin := &Admin{
User: User{
Name: "AriesDevil",
Email: "[email protected]",
},
Level: "master",
}
外部类可以直接访问内部类的变量与方法,更加符合"组合而非继承"的思想
type user struct{
name string
email string
}
type admin struct{
user
level string
}
func (u user) sayHello(){
fmt.Println("Hello,i am a user")
}
func main(){
ad := admin{user{"张三", "[email protected]"}, "管理员"}
fmt.Println(ad.name)
fmt.Println(ad.user.name)
fmt.Println(ad.sayHello()) //直接调用
}
类似于python当中的鸭子思想
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
因此go中实现接口无需显式声明该类型实现了哪个接口
直接在类方法中实现即可.
编程逻辑: 考虑用到哪个方法再考虑属于哪个类型, 而不是在类型内部定义方法.
type Phone interface {
call()
}
type NokiaPhone struct {
}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
if init; condition{
}else{
}
条件表达式之前可接初始化赋值, 但变量仅可在该if…else中使用
func main() {
if a := "chris"; a == "chris" {
fmt.Println(a)
}
fmt.Println(a)
}
第二个输出将导致undefined错误
type在以下五种情况中会用到,具体用法在以下相关段落展开.
idea: go中的循环只有for
for{
}
for 条件{
}
3.for用法
for 初始化;条件;下一次循环前操作{
}
for _, num := range nums {
sum += num
}
官方文档中的定义,defer/recover/panic功能上属于代码运行流程控制. 类似于try…catch…finally,但又有所不同
tips:
func a() {
i := 0
defer fmt.Println(i)
i++
return
}
eg:
package main
import "fmt"
func main(){
defer func(){
fmt.Println("1")
}()
defer func(){
fmt.Println("2")
}()
defer func(){
fmt.Println("3")
}()
}
输出:
3
2
1
常见用法:
eg1:
释放锁
mu.Lock()
defer mu.Unlock()
printing a footer:
eg2:
渲染页面底部
printHeader()
defer printFooter()
1.相当于catch()
2. recover()只能出现在defer中(可理解为在函数常规流程执行完毕之后).如果当前goroutine发生panic(),recover()可捕获panic(),阻止panic()继续向外抛出
3. 正常情况下,recover()返回nil
4. recover()捕获的值即为panic()的参数值.
eg:
package main
import (
"fmt"
)
func foo() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Got this from panic:", r)
}
}()
panic("Things from panic")
}
func main() {
foo()
}
输出:
Got this from panic: Things from panic
type User struct {
username string
password string
}
func (p *User) init(username string ,password string) (*User,string) {
if ""==username || ""==password {
return p,p.Error()
}
p.username = username
p.password = password
return p,""}
func (p *User) Error() string {
return "Usernam or password shouldn't be empty!"}
}
func main() {
var user User
user1, _ :=user.init("","");
fmt.Println(user1)
}
.go文件不可命名为"_test"结尾
跟在python当中一样,golang同样支持闭包,通俗地说闭包就是一个函数的返回值也是函数,函数的内容可在运行时才决定,无需编译时就决定.
例子:
func getSequence() func() int {
i:=0
return func() int {
i+=1
return i
}
}
package xxx //指定包
type xxx //定义所要用到的静态类型:定义结构体、定义接口、类型定义、类型别名
func xxx // 一般函数方法
func main //主函数
make只负责chan、map以及切片:
func make(t Type, size ...IntegerType) Type//返回一个对象
make([]T, length, capacity)//capacity可选
new只返回指针:
// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type
一个指向空对象的指针, 并不常用
带参数的channel具有缓冲区, 最重要的是可以实现send和recv的异步,也是和python协程最大的不同
待分析:(chan切换线程的时机)
package main
import (
"fmt"
)
func main() {
//ch := make(chan string)
ch := make(chan string)
go setData(ch)
fmt.Println("ready to recv")
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
func setData(ch chan string) {
fmt.Println("put 'test' into chan")
ch <- "test"
fmt.Println("put 'hello wolrd' into chan")
ch <- "hello wolrd"
fmt.Println("put '123' into chan")
ch <- "123"
fmt.Println("put '456' into chan")
ch <- "456"
fmt.Println("put '789' into chan")
ch <- "789"
//fmt.Println("put '789' into chan")
}
输出:
ready to recv
put 'test' into chan
put 'hello wolrd' into chan
test
hello wolrd
put '123' into chan
put '456' into chan
123
456
put '789' into chan
789
与操作系统中多路复用的select/epoll当中select的作用基本相同.可以理解为当前线程从多个channel当中存取数据的select操作.