从Python到Golang学习笔记

从本科学过C/C++和java,但是实际项目中用的多的是python. 因此python深刻影响了我的编程思维, 从python转到Go, 让我总结了一些学习笔记.

静态/动态

如果你在python之前没有C或JAVA等静态语言的代码量积累, 接触go之后, 编程习惯上最大的各种不同基本都可以归根于动态语言静态语言的区别.
简单来说,两门语言各自的编译解释的方式不同,造成了编程时思维的不同.python是一行一行地编写,而go需要把当前的.go文件当做一个整体来编写.

可能体现在:

  1. 不引入无用的包: python中import作为独立的语句执行, 但是Go中import的包必须考虑在整个程序中是否有用到
  2. 每一个定义的变量都必须被使用,否则会造成编译错误:
    …num declared and not used
  3. Go编译器会以happen before的原则调整语句执行顺序.

强编程规范(相比python)

相比python更加严格一些.

  1. 大括号的位置
  2. 类型写在变量之后
  3. 三种赋值写法(后面补充)

import的用法

开头提及,不能import没有用到的模块.import使用上也有小细节.

import带点"."

可以直接使用模块中的变量与方法

import . "fmt"
func main(){
    Println("Hello")//而不是fmt.Println("Hello")
}

import带别名

使用别名代替,如同python的import…as…

import f "fmt"
func main(){
    f.Println("Hello")
}

import带下划线"_"

仅执行到模块的init()文件,而不能使用模块的变量或方法.


import _ "fmt"

func main() {
    fmt.Println("helo")    // error

可以理解为,python文件当中常常需要做

if __name__=="__main__":
    ...
    ...

这样的判断,因为有些代码你希望它在当前模块被import时并不运行,只在自身作为mian程序运行时才执行.
显然使用if判断是python当中一个略显粗糙的设计,golang通过丰富import的用法完善这个功能.

var关键字与赋值细节

var 变量名 [类型] [=] [初始值] 

“:=” : 便捷写法, 完成类型声明与赋值.(因此要求变量既未被声明也未被赋值, 换言之, 这个变量在上文还没出现过)

下划线_: 不被使用的值
如python中,

for _ in this_list 完成遍历, 但不使用列表中的值

a, _, c, = this_func() // 不被使用的返回值

python已有约定俗成的用法,只是go把它列入了编程规范.

运算符

  1. 多了自增++和自减–

数据类型

  1. []Type : 表示数组

面向对象

struct替代class, 因此没有构造函数析构函数
接口interface替代继承关系

struct使用方法:

variable_name := structure_variable_type {value1, value2...valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

定义类方法

func (this Type) func_type(){
    
}

Go函数传参只有值传递

不同于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…else语句

if init; condition{
    
}else{
    
}

条件表达式之前可接初始化赋值, 但变量仅可在该if…else中使用

func main() {
	if a := "chris"; a == "chris" {
		fmt.Println(a)
	}
	fmt.Println(a)
}

第二个输出将导致undefined错误

type关键字

type在以下五种情况中会用到,具体用法在以下相关段落展开.

  • 定义结构体
  • 定义接口
  • 类型定义
  • 类型别名
  • 类型查询

for关键字

idea: go中的循环只有for

  1. while True用法
for{
    
}
  1. do…while…用法
for 条件{
    
}

3.for用法

for 初始化;条件;下一次循环前操作{
    
}
  1. foreach用法
    range可操作切片(slice)\数组\map
for _, num := range nums {
        sum += num
    }

defer recovery panic error

官方文档中的定义,defer/recover/panic功能上属于代码运行流程控制. 类似于try…catch…finally,但又有所不同

defer

tips:

  1. 常用来处理清理环境的操作,如关闭文件,断开连接,清空缓存区等.相当于finally
  2. 多个defer存在, 以先进后出的栈形式运行.
  3. defer调用的函数的参数在defer语句出现时就确定参数值,只是defer等到函数最后才执行.
    如下面函数最终返回0
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()

panic

  1. 等同于抛出异常.发生异常时中断当前函数,对函数调用者来说就像直接调用了panic一样.
  2. 相当于Error,有两种发生panic()的情况,既可以主动调用(即python当中的raise),程序运行时错误时也发生panic()

recover

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

Error杂记

  1. 若当前struct结构体实现了Error, 使用fmt.Println()输出结构体对象时,会调用Error(待解决未确定)
    例子:
    fmt.Println 打印结构体的时候,会把其中的 error 的返回的信息打印出来。
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)
}

总结

  1. defer在函数末尾执行,执行"clean-up"操作
  2. recover()只能在defer中,用于捕获panic()抛出的异常
  3. panic()中断当前goroutine,抛出错误信息.

测试

.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和new

make只负责chan、map以及切片:

func make(t Type, size ...IntegerType) Type//返回一个对象
  1. 切片
make([]T, length, capacity)//capacity可选
  1. map
  2. 通道chan

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

一个指向空对象的指针, 并不常用

goroutine

  1. 实际上与python协程相似
  2. goroutine本质上是协程,可以理解为不受内核调度,而受go调度器管理的线程。

make(chan string)和make(chan string, 1)

带参数的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

与操作系统中多路复用的select/epoll当中select的作用基本相同.可以理解为当前线程从多个channel当中存取数据的select操作.

其他

  1. 下划线"_"命名的变量不可用.其实在python已经约定俗成.

细节总结

  • 规范的语法(不需要符号表来解析)
  • 垃圾回收(独有)
  • 无头文件
  • 明确的依赖
  • 无循环依赖
  • 常量只能是数字
  • int和int32是两种类型
  • 字母大小写设置可见性(letter case sets visibility)
  • 任何类型(type)都有方法(不是类型)
  • 没有子类型继承(不是子类)
  • 包级别初始化以及明确的初始化顺序
  • 文件被编译到一个包里
  • 包package-level globals presented in any order
  • 没有数值类型转换(常量起辅助作用)
  • 接口隐式实现(没有“implement”声明)
  • 嵌入(不会提升到超类)
  • 方法按照函数声明(没有特别的位置要求)
  • 方法即函数
  • 接口只有方法(没有数据)
  • 方法通过名字匹配(而非类型)
  • 没有构造函数和析构函数
  • postincrement(如++i)是状态,不是表达式
  • 没有preincrement(i++)和predecrement
  • 赋值不是表达式
  • 明确赋值和函数调用中的计算顺序(没有“sequence point”)
  • 没有指针运算
  • 内存一直以零值初始化
  • 局部变量取值合法
  • 方法中没有“this”
  • 分段的堆栈
  • 没有静态和其它类型的注释
  • 没有模板
  • 内建string、slice和map
  • 数组边界检查

你可能感兴趣的:(Go,python学习笔记)