前言
Go语言定义
Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态、强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC,结构形态及 CSP-style 并发计算。适用范围
一、基础语法
1.1 变量、常量、nil与零值、方法、包、可见性、指针
1.1.1 变量声明
var关键字声明,且需要注意的是,与大多数强类型语言不同,Go语言的声明变量类型位于变量名称的后面。Go语句结束不需要分号。
var num int
var result string = "this is result"
:=赋值。
num := 3 等同于 var num int = 3
1.1.2 常量声明
const来声明一个常量,一个常量在声明后不可改变。
const laugh string = "go"
1.1.3 nil与零值
-
数值类型为 0, -
布尔类型为 false, -
字符串为 ""(空字符串)。
1.1.4 方法、包
Go中方法的定义
func MethodName(p1 Parm, p2 Parm) int{}
//学习一个语言应该从Hello World开始!package mainimport "fmt"func main() {fmt.Println("Hello World!")// Hello World!fmt.Println(add(3, 5)) //8var sum = add(3, 5)}func add(a int, b int) int{return a+b;}
多个返回值
add 函数只支持非负整数相加,传入负数则会报错。
//返回值只定义了类型 没有定义返回参数func add(a, b int) (int, error) {if a < 0 || b < 0 {err := errors.New("只支持非负整数相加")return 0, err}a *= 2b *= 3return a + b, nil}//返回值还定义了参数 这样可以直接return 并且定义的参数可以直接使用 return时只会返回这两个参数func add1(a, b int) (z int, err error) {if a < 0 || b < 0 {err := errors.New("只支持非负整数相加")return //实际返回0 err 因为z只定义没有赋值 则nil值为0}a *= 2b *= 3z = a + breturn //返回 z err}func main() {x, y := -1, 2z, err := add(x, y)if err != nil {fmt.Println(err.Error())return}fmt.Printf("add(%d, %d) = %d\n", x, y, z)}
变长参数
func myfunc(numbers ...int) {for _, number := range numbers {fmt.Println(number)}}slice := []int{1, 2, 3, 4, 5}//使用...将slice打碎传入myfunc(slice...)
包与可见性
private、protected 和 public 这些关键字来修饰其可见性。
domain的文件夹下有3个.go文件,则三个文件中的package都应为domain,其中程序的入口main方法所在的文件,包为main
//定义了此文件属于 main 包package main//通过import导入标注库中包import "fmt"func main() {fmt.Println("Hello World!")// Hello World!fmt.Println(add(3, 5)) //8var sum = add(3, 5)}func add(a int, b int) int{return a+b;}
1.1.5 指针
func main() {i := 0//使用&来传入地址fmt.Println(&i) //0xc00000c054var a, b int = 3 ,4//传入 0xc00000a089 0xc00000a090fmt.Println(add(&a, &b))}//使用*来声明一个指针类型的参数与使用指针func add(a *int, b *int)int{//接收到 0xc00000a089 0xc00000a090//前往 0xc00000a089位置查找具体数据 并取赋给xx := *a//前往 0xc00000a090位置查找具体数据 并取赋给yy := *breturn x+y}
1.2 条件、循环、分支
1.2.1 条件
// ifif condition {// do something}// if...else...if condition {// do something} else {// do something}// if...else if...else...if condition1 {// do something} else if condition2 {// do something else} else {// catch-all or default}
1.2.2 循环
sum := 0//普通for循环for i := 1; i <= 100; i++ {sum += i}//无限循环for{sum++if sum = 100{break;}}//带条件的循环for res := sum+1; sum < 15{sum++res++}//使用kv循环一个map或一个数组 k为索引或键值 v为值 k、v不需要时可以用_带替for k, v := range a {fmt.Println(k, v)}
1.2.3 分支
score := 100switch score {case 90, 100:fmt.Println("Grade: A")case 80:fmt.Println("Grade: B")case 70:fmt.Println("Grade: C")case 65:fmt.Println("Grade: D")default:fmt.Println("Grade: F")}
1.3 数组、切片、字典
1.3.1 数组
//声明var nums [3]int//声明并初始化var nums = [3]int{1,2,3} <==> nums:=[3]int{1,2,3}//使用for sum := 0, i := 0;i<10{sum += nums[i]i++}//修改值num[0] = -1
1.3.2 切片
长度、容量
切片的长度就是它所包含的元素个数。
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数。
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取。
创建切片
//声明一个数组var nums =[3]int{1, 2, 3}//0.直接声明var slice =[]int{0, 1, 2}//1.从数组中引用切片 其中a:b是指包括a但不包括bvar slice1 = nums[0:2] //{1,2}//如果不写的则默认为0(左边)或最大值(右边)var slice2 = slice1[:2] <==> var slice2 = slice1[0:] <==>var slice2 = slice1[:]//2.使用make创建Slice 其中int为切片类型,4为其长度,5为容量slice3 := make([]int, 5)slice4 := make([]int, 4, 5)
动态操作切片
//使用append向切片中动态的添加元素func append(s []T, vs ...T) []Tslice5 := make([]int, 4, 5) //{0, 0, 0, 0}slice5 = append(slice5, 1) //{0,0,0,0,1}//删除第一个0sliece5 = slice5[1:]
切片的常用场景
//声明切片var userIds = []int{}//模拟获取所有用户IDfor i := 0; i< 100{userIds = append(userIdS, i);i++;}//对用户信息进行处理for k,v := range userIds{userIds[k] = v++}
1.3.3 字典
字典的声明与初始化
//string为键类型,int为值类型maps := map[string]int{"java" : 1,"go" : 2,"python" : 3,}//还可以通过make来创建字典 100为其初始容量 超出可扩容maps = make(map[string]int, 100)
字典的使用场景
//直接使用fmt.Println(maps["java"]) //1//赋值maps["go"] = 4//取值 同时判断map中是否存在该键 ok为bool型value, ok := maps["one"]if ok { // 找到了// 处理找到的value}//删除delete(testMap, "four")
二、面向对象编程
2.1 Go语言中的类
2.1.1 类的声明与初始化
struct关键字可以从功能上类比为 面向对象语言中的“类” 。比如要定义一个学生类,可以这么做:
type Student struct {id intname stringmale boolscore float64}//定义了一个学生类,属性有id name等,每个属性的类型都在其后面//定义学生类的构造方法func NewStudent(id uint, name string, male bool, score float64) *Student {return &Student{id, name, male, score}}//实例化一个类对象student := NewStudent(1, "学院君", 100)fmt.Println(student)
2.1.2 成员方法
//在方法名前,添加对应的类,即可认为改方法为该类的成员方法。func (s Student) GetName() string {return s.name}//注意这里的Student是带了*的 这是因为在方法传值过程中 存在着值传递与引用传递 即指针的概念 当使用值传递时 编译器会为该参数创建一个副本传入 因此如果对副本进行修改其实是不生效的 因为在执行完此方法后该副本会被销毁 所以此处应该是用*Student 将要修改的对象指针传入 修改值才能起作用func (s *Student) SetName(name string) {//这里其实是应该使用(*s).name = name,因为对于一个地址来说 其属性是没意义的 不过这样使用也是可以的 因为编译器会帮我们自动转换s.name = name}
2.2 接口
2.2.1 传统侵入式接口实现
// 声明一个'iTemplate'接口interface iTemplate{public function setVariable($name, $var);public function getHtml($template);}// 实现接口// 下面的写法是正确的class Template implements iTemplate{private $vars = array();public function setVariable($name, $var){$this->vars[$name] = $var;}public function getHtml($template){foreach($this->vars as $name => $value) {$template = str_replace('{' . $name . '}', $value, $template);}return $template;}}
iTemplate2 声明了与 iTemplate 完全一样的接口方法,甚至名字也叫 iTemplate,只不过位于不同的命名空间下,编译器也会认为上面的类 Template 只实现了 iTemplate 而没有实现 iTemplate2 接口。
SessionHandlerInterface {/* 方法 */abstract public close ( void ) : boolabstract public destroy ( string $session_id ) : boolabstract public gc ( int $maxlifetime ) : intabstract public open ( string $save_path , string $session_name ) : boolabstract public read ( string $session_id ) : stringabstract public write ( string $session_id , string $session_data ) : bool}
gc 方法根本不需要实现,又比如 close 方法对于大部分驱动来说,也是没有什么意义的。
-
一个接口需要声明哪些接口方法? -
如果多个类实现了相同的接口方法,应该如何设计接口?比如上面这个 SessionHandlerInterface,有没有必要拆分成多个更细分的接口,以适应不同实现类的需要?
2.2.2 Go 语言的接口实现
implement 这种关键字显式声明该类实现了哪个接口,一个类只要实现了某个接口要求的所有方法,我们就说这个类实现了该接口。
File 类,并实现了 Read()、Write()、Seek()、Close() 四个方法:
type File struct {// ...}func (f *File) Read(buf []byte) (n int, err error)func (f *File) Write(buf []byte) (n int, err error)func (f *File) Seek(off int64, whence int) (pos int64, err error)func (f *File) Close() error
interface 来声明接口,以示和结构体类型的区别,花括号内包含的是待实现的方法集合):
type IFile interface {Read(buf []byte) (n int, err error)Write(buf []byte) (n int, err error)Seek(off int64, whence int) (pos int64, err error)Close() error}type IReader interface {Read(buf []byte) (n int, err error)}type IWriter interface {Write(buf []byte) (n int, err error)}type ICloser interface {Close() error}
File 类并没有显式实现这些接口,甚至根本不知道这些接口的存在,但是我们说 File 类实现了这些接口,因为 File 类实现了上述所有接口声明的方法。当一个类的成员方法集合包含了某个接口声明的所有方法,换句话说,如果一个接口的方法集合是某个类成员方法集合的子集,我们就认为该类实现了这个接口。
-
其一,Go 语言的标准库不需要绘制类库的继承/实现树图,在 Go 语言中,类的继承树并无意义,你只需要知道这个类实现了哪些方法,每个方法是干什么的就足够了。 -
其二,定义接口的时候,只需要关心自己应该提供哪些方法即可,不用再纠结接口需要拆得多细才合理,也不需要为了实现某个接口而引入接口所在的包,接口由使用方按需定义,不用事先设计,也不用考虑之前是否有其他模块定义过类似接口。
三、并发与多线程
3.1 Goroutine
func say(s string) {fmt.Println(s)}func main() {//通过 go 关键字新开一个协程go say("world")say("hello")}
//给类SafeCounter添加锁type SafeCounter struct {v map[string]intmux sync.Mutex}// Inc 增加给定 key 的计数器的值。func (c *SafeCounter) Inc(key string) {//给该对象上锁c.mux.Lock()// Lock 之后同一时刻只有一个 goroutine 能访问 c.vc.v[key]++//解锁c.mux.Unlock()}
3.2 Channel
ch := make(chan int) 声明一个int型的Channel,两个协程之间可以通过ch进行int数据通信。
ch <- v // 将 v 发送至信道 ch。v := <-ch // 从 ch 接收值并赋予 v。
package mainimport "fmt"func sum(s []int, c chan int) {sum := 0for _, v := range s {sum += v}c <- sum // 将和送入 c}//对于main方法来说 相当于就是开启了一个协程func main() {s := []int{7, 2, 8, -9, 4, 0}c := make(chan int)//通过go关键字开启两个协程 将chaneel当做参数传入go sum(s[:len(s)/2], c)go sum(s[len(s)/2:], c)//通过箭头方向获取或传入信息x, y := <-c, <-c // 从 c 中接收fmt.Println(x, y, x+y)}
四、错误处理
4.1 error
error 接口,该接口的定义非常简单:
type error interface {Error() string}
Error() 方法,用于返回字符串类型的错误消息。对于大多数函数或类方法,如果要返回错误,基本都可以定义成如下模式 —— 将错误类型作为第二个参数返回:
func Foo(param int) (n int, err error) {// ...}
n, err := Foo(0)if err != nil {// 错误处理} else{// 使用返回值 n}
4.2 defer
func ReadFile(filename string) ([]byte, error) {f, err := os.Open(filename)if err != nil {return nil, err}//无论结果如何 都要关闭文件流defer f.Close()var n int64 = bytes.MinReadif fi, err := f.Stat(); err == nil {if size := fi.Size() + bytes.MinRead; size > n {n = size}}return readAll(f, n)}
4.3 panic
a := 1/0。
4.4 recover
package mainimport ("fmt")func divide() {//通过defer,确保该方法只要执行完毕都要执行该匿名方法defer func() {//进行异常捕获if err := recover(); err != nil {fmt.Printf("Runtime panic caught: %v\n", err)}}()var i = 1var j = 0k := i / jfmt.Printf("%d / %d = %d\n", i, j, k)}func main() {divide()fmt.Println("divide 方法调用完毕,回到 main 函数")}
fmt.Printf("%d / %d = %d\n", i, j, k)语句并没有执行到,因为代码执行到他的上一步已经出现异常导致该方法提前结束。4 recover
package mainimport ("fmt")func divide() {//通过defer,确保该方法只要执行完毕都要执行该匿名方法defer func() {//进行异常捕获if err := recover(); err != nil {fmt.Printf("Runtime panic caught: %v\n", err)}}()var i = 1var j = 0k := i / jfmt.Printf("%d / %d = %d\n", i, j, k)}func main() {divide()fmt.Println("divide 方法调用完毕,回到 main 函数")}
fmt.Printf("%d / %d = %d\n", i, j, k)语句并没有执行到,因为代码执行到他的上一步已经出现异常导致该方法提前结束。五、总结


