go语言特性理解--变量/函数/面向对象/并发编程/错误处理
1. 变量
变量的声明很像 javascript,使用 var关键字。注意:go是静态类型的语言
//声明初始化一个变量
var x int = 100
var str string = "hello world"</pre>
//声明初始化多个变量
var i, j, k int = 1, 2, 3//不用指明类型,通过初始化值来推导
var b = true //bool型
2. 函数
相比C语言来说,Go语言是反过来声明变量类型和函数返回值的。
package main
import "fmt"func max(a int, b int) int { //注意参数和返回值是怎么声明的if a > b {return a}return b
}func main(){fmt.Println(max(4, 5))
}
3. 面向对象
结构体
Go的结构体和C的基本上一样,不过在初始化时有些不一样,Go支持带名字的初始化。
type Person struct {name stringage intemail string
}func main() {//初始化person := Person{"Tom", 30, "tom@gmail.com"}person = Person{name:"Tom", age: 30, email:"tom@gmail.com"}fmt.Println(person) //输出 {Tom 30 tom@gmail.com}pPerson := &personfmt.Println(pPerson) //输出 &{Tom 30 tom@gmail.com}pPerson.age = 40person.name = "Jerry"fmt.Println(person) //输出 {Jerry 40 tom@gmail.com}
}
结构体方法
Go语言中没有public, protected, private的关键字,所以,如果你想让一个方法可以被别的包访问的话,你需要把这个方法的第一个字母大写。这是一种约定。
type rect struct {width, height int
}func (r *rect) area() int { //求面积return r.width * r.height
}func (r *rect) perimeter() int{ //求周长return 2*(r.width + r.height)
}func main() {r := rect{width: 10, height: 15}fmt.Println("面积: ", r.area())fmt.Println("周长: ", r.perimeter())rp := &rfmt.Println("面积: ", rp.area())fmt.Println("周长: ", rp.perimeter())
}
接口
//---------- 接 口 --------//
type shape interface {area() float64 //计算面积perimeter() float64 //计算周长
}//--------- 长方形 ----------//
type rect struct {width, height float64
}func (r *rect) area() float64 { //面积return r.width * r.height
}func (r *rect) perimeter() float64 { //周长return 2*(r.width + r.height)
}//----------- 圆 形 ----------//
type circle struct {radius float64
}func (c *circle) area() float64 { //面积return math.Pi * c.radius * c.radius
}func (c *circle) perimeter() float64 { //周长return 2 * math.Pi * c.radius
}// ----------- 接口的使用 -----------//
func interface_test() {r := rect {width:2.9, height:4.8}c := circle {radius:4.3}s := []shape{&r, &c} //通过指针实现for _, sh := range s {fmt.Println(sh)fmt.Println(sh.area())fmt.Println(sh.perimeter())}
}
4. 并发编程
Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。
CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。
Go的CSP并发模型,是通过goroutine和channel来实现的。
GoRoutine
GoRoutine主要是使用go关键字来调用函数,你可以简单的把go关键字调用的函数想像成pthread_create。
使用for循环创建了3个线程,每个线程使用一个随机的Sleep时间,然后在routine()函数中会输出一些线程执行的时间信息。
package mainimport "fmt"
import "time"
import "math/rand"func routine(name string, delay time.Duration) {t0 := time.Now()fmt.Println(name, " start at ", t0)time.Sleep(delay)t1 := time.Now()fmt.Println(name, " end at ", t1)fmt.Println(name, " lasted ", t1.Sub(t0))
}func main() {//生成随机种子rand.Seed(time.Now().Unix())var name stringfor i:=0; i<3; i++{name = fmt.Sprintf("go_%02d", i) //生成ID//生成随机等待时间,从0-4秒go routine(name, time.Duration(rand.Intn(5)) * time.Second)}//让主进程停住,不然主进程退了,goroutine也就退了var input stringfmt.Scanln(&input)fmt.Println("done")
}
运行的结果可能是:
go_00 start at 2012-11-04 19:46:35.8974894 +0800 +0800
go_01 start at 2012-11-04 19:46:35.8974894 +0800 +0800
go_02 start at 2012-11-04 19:46:35.8974894 +0800 +0800
go_01 end at 2012-11-04 19:46:36.8975894 +0800 +0800
go_01 lasted 1.0001s
go_02 end at 2012-11-04 19:46:38.8987895 +0800 +0800
go_02 lasted 3.0013001s
go_00 end at 2012-11-04 19:46:39.8978894 +0800 +0800
go_00 lasted 4.0004s
Channel
Channel就是用来实现goroutine之间通信的。
package mainimport "fmt"func main() {//创建一个string类型的channelchannel := make(chan string)//创建一个goroutine向channel里发一个字符串go func() { channel <- "hello" }()msg := <- channelfmt.Println(msg)
}[
注意 main()本身也是运行了一个goroutine。
messages:= make(chan int) 这样就声明了一个阻塞式的无缓冲的通道
chan 是关键字 代表我要创建一个通道
GO并发模型的实现原理
无论是Windows还是Linux,GoRoutine基本上来说是用操作系统的线程来实现的。不过,goroutine有个特性,也就是说,如果一个goroutine没有被阻塞,那么别的goroutine就不会得到执行。
用户线程
用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程(用户进程)本身,内核并不能看到用户线程,内核并不知道用户线程的存在。
用户级线程内核的切换由用户态程序自己控制内核切换(通过系统调用来获得内核提供的服务),不需要内核干涉,少了进出内核态的消耗,但不能很好的利用多核Cpu。
内核线程
内核级线程切换由内核控制,当线程进行切换的时候,由用户态转化为内核态。切换完毕要从内核态返回用户态。可以很好的运用多核CPU。
两级线程模型
这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂,和内核级线程模型类似,一个进程中可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应;这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。
M个用户线程对应N个内核线程,缺点增加了调度器的实现难度。
Go语言的线程模型就是一种特殊的两级线程模型(GPM调度模型)。
5. 错误处理
错误处理一直以一是编程必需要面对的问题,错误处理如果做的好的话,代码的稳定性会很好。不同的语言有不同的出现处理的方式。
Java的错误处理
Java语言使用 try-catch-finally 通过使用异常的方式来处理错误,其实,这比起C语言的错处理进了一大步,使用抛异常和抓异常的方式可以让我们的代码有这样的一些好处:
- 函数接口在 input(参数)和 output(返回值)以及错误处理的语义是比较清楚的。
- 正常逻辑的代码可以与错误处理和资源清理的代码分开,提高了代码的可读性。
- 异常不能被忽略(如果要忽略也需要 catch 住,这是显式忽略)。
- 在面向对象的语言中(如 Java),异常是个对象,所以,可以实现多态式的 catch。
- 与状态返回码相比,异常捕捉有一个显著的好处是,函数可以嵌套调用,或是链式调用。比如:
int x = add(a, div(b,c));
Pizza p = PizzaBuilder().SetSize(sz).SetPrice(p)...;
go语言的错误处理
Go 语言的函数支持多返回值,所以,可以在返回接口把业务语义(业务返回值)和控制语义(出错返回值)区分开来。Go 语言的很多函数都会返回 result, err 两个值,于是:
- 参数上基本上就是入参,而返回接口把结果和错误分离,这样使得函数的接口语义清晰;
- 而且,Go 语言中的错误参数如果要忽略,需要显式地忽略,用 _ 这样的变量来忽略;
- 另外,因为返回的 error 是个接口(其中只有一个方法 Error(),返回一个 string ),所以你可以扩展自定义的错误处理。
type error interface {Error() string
}
错误处理代码示例:
package mainimport "fmt"
import "errors"//自定义的出错结构
type myError struct {arg interrMsg string
}
//实现Error接口
func (e *myError) Error() string {return fmt.Sprintf("%d - %s", e.arg, e.errMsg)
}//两种出错
func error_test(arg int) (int, error) {if arg < 0 {return -1, errors.New("Bad Arguments - negtive!")}else if arg >256 {return -1, &myError{arg, "Bad Arguments - too large!"}}return arg*arg, nil
}//相关的测试
func main() {for _, i := range []int{-1, 4, 1000} {if r, e := error_test(i); e != nil {fmt.Println("failed:", e)} else {fmt.Println("success:", r)}}
}
另外,如果一个函数返回了多个不同类型的 error,你也可以使用下面这样的方式:
if err != nil {switch err.(type) {case *json.SyntaxError:...case *ZeroDivisionError:...case *NullPointerError:...default:...}
}
我们可以看到,Go语言的错误处理的的方式,本质上是返回值检查,但是他也兼顾了异常的一些好处 – 对错误的扩展。
资源清理Defer
出错后是需要做资源清理的,不同的编程语言有不同的资源清理的编程模式:
- C语言 – 使用的是 goto fail; 的方式到一个集中的地方进行清理
- C++语言- 一般来说使用 RAII模式,通过面向对象的代理模式,把需要清理的资源交给一个代理类,然后在析构函数来解决。
- Java语言 – 可以在finally 语句块里进行清理。
- Go语言 – 使用 defer 关键词进行清理。
func Close(c io.Closer) {err := c.Close()if err != nil {log.Fatal(err)}
}func main() {r, err := Open("a")if err != nil {log.Fatalf("error opening 'a'\n")}defer Close(r) // 使用defer关键字在函数退出时关闭文件。r, err = Open("b")if err != nil {log.Fatalf("error opening 'b'\n")}defer Close(r) // 使用defer关键字在函数退出时关闭文件。
}
错误调用栈
除此之外,上述写法都在返回错误时都丢掉了调用栈这个重要的诊断信息。我们需要更灵活、更通用的方式来应对此类问题。
github.com/pkg/errors 提供了一种好用的错误处理方式,它提供了三个关键方法:
- Wrap 方法用来包装底层错误,增加上下文文本信息并附加调用栈。 一般用于包装对第三方代码(标准库或第三方库)的调用。
- WithMessage 方法仅增加上下文文本信息,不附加调用栈。 如果确定错误已被 Wrap 过或不关心调用栈,可以使用此方法。 注意:不要反复 Wrap ,会导致调用栈重复
- Cause方法用来判断底层错误 。
import ("database/sql""fmt""github.com/pkg/errors"
)func foo() error {return errors.Wrap(sql.ErrNoRows, "foo failed")
}func bar() error {return errors.WithMessage(foo(), "bar failed")
}func main() {err := bar()if errors.Cause(err) == sql.ErrNoRows {fmt.Printf("data not found, %v\n", err)fmt.Printf("%+v\n", err)return}if err != nil {// unknown error}
}
/*Output:
data not found, bar failed: foo failed: sql: no rows in result set
sql: no rows in result set
foo failed
main.foo/usr/three/main.go:11
main.bar/usr/three/main.go:15
main.main/usr/three/main.go:19
runtime.main...
*/
go语言特性理解--变量/函数/面向对象/并发编程/错误处理相关推荐
- 通过ThreadPoolExecutor与ForkJoinPool比较,分别对比其execute ,submit 等方法提交线程池任务的区别,来深入理解线程池及并发编程
前言 以前使用线程池,对execute . submit 等方法提交线程池任务的区别比较模糊,现在通过ThreadPoolExecutor与ForkJoinPool比较,分别对比其execute ,s ...
- java 变量锁_并发编程高频面试题:可重入锁+线程池+内存模型等(含答案)
对于一个Java程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一.因为并发编程是Java语言中最为晦涩的知识点,它涉及操作系统.内存.CPU.编程语言等多方面的基础能力,更为考验一个程序 ...
- python笔记2(函数 面向对象 文件编程 上下文管理器)
记录python听课笔记 文章目录 记录python听课笔记 一,函数 1.介绍python里的函数 2.用户自定义函数 3.变量的作用域 4.参数的传递 5.参数的默认值 6.向函数内部批量传递数据 ...
- 【Groovy】Groovy 动态语言特性 ( Groovy 中函数实参自动类型推断 | 函数动态参数注意事项 )
文章目录 前言 一.Groovy 中函数实参自动类型推断 二.函数动态参数注意事项 三.完整代码示例 前言 Groovy 是动态语言 , Java 是静态语言 ; 本篇博客讨论 Groovy 中 , ...
- C语言不变值变量函数,C语言中的RAND 函数产生的数值不变
满意答案 9yuebook 推荐于 2017.12.15 采纳率:56% 等级:7 已帮助:312人 rand和srand的用法 首先我们要对rand&srand有个总体的看法:sran ...
- java猫抓老鼠_用猫抓老鼠的实例理解java中面向对象的编程与类和对象以及方法的概念...
今天看到马士兵讲的关于面向对象编程的思路,用了一个猫抓老鼠的例子,我觉得这个例子非常形象,于是写在这里,方便学习理解和以后查看 class cat{ //声明一个类–"猫" int ...
- java 变量共享_Java并发编程之共享变量
可见性 如果一个线程对共享变量值的修改,能够及时的被其他线程看到,叫做共享变量的可见性. Java 虚拟机规范试图定义一种 Java 内存模型(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,让 ...
- R语言与面向对象的编程(3):R6类
专注系列化.高质量的R语言教程 (本号已支持快捷转载,无需白名单即可转载) 本系列将介绍R语言中三个与面向对象的编程(Object-Oriented Programming,OOP)相关的工具包:pr ...
- 【Java 并发编程】 03 万恶的 Bug 起源—并发编程的三大特性
今天让我们一起走进并发编程中万恶的Bug起源-并发编程中三大特性.今天学习目标如下: 并发编程的三大特性都要哪些 ? 并发编程三大特性的由来? 如何解决并发编程三大特性问题? 基本概念 原子性:一组操 ...
最新文章
- win7如何设置某个软件不弹出用户账户控制
- DataRow的序列化问题
- Stm32 IAP程序编写及用户程序编写
- Oracle入门(十四A)之PL/SQL 基本结构
- C while 循环
- 利用边缘监督信息加速Mask R-CNN实例分割训练
- c 读取url中的html文本,如何使用Guzzlehttp获取给定url的html内容
- 量子计算机网络指数时间,科普:量子计算机是这样计算的
- 海康rtsp视频流直播-kurento
- 2018python教程百度云盘_python基础教程视频网盘_python教学视频2018百度云
- LoadRunner教程(16)-LoadRunner SLA分析
- 凭据分配没有加密oracle_两种方法解决远程桌面出现“这可能是由于CredSSP加密Oracle修正”的问题-网络教程与技术
-亦是美网络...
- WindRiver Linux Yocto Project Note
- 纯python实现线性回归——以各国人均GDP变化为例
- zabbix 监控h3c 10508 交换机光衰值
- matlab has encountered,matlab运行程序时出现“matlab has encountered an internal problem
- 简单实用计划提醒便签软件 界面简洁功能一目了然
- 嵌入式物联网软件开发实战系列(STM32+FreeRTOS)
- win7虚拟计算机名,电脑高手必会技能:VMware虚拟机安装win7教程
- veek-soc-iii_所以您想使用招聘人员第III部分-警告
热门文章
- messagebox 全部使用_商业篇 | 使用python开发性格分析工具卖钱
- c++ qt获取电脑的内存_QT开发(十四)——QT绘图系统
- svpwm仿真_【好物推荐】《现代永磁同步电机控制原理及MATLAB仿真》
- linux拷贝到新建文件夹命令行,Linux创建文件touch,复制文件cp,tab补全,链接文件ln命令...
- java 模拟post上传文件_Java模拟post请求上传文件
- xml和html是兄弟还是父子?
- 第一代计算机主要用于科学计算和数据处理,计算机一级第一章练习题.doc
- c语言通讯录程序线性表,数据结构(C语言)课设1——单位员工通讯录管理系统(线性表应用)...
- Java 蓝桥杯 字符串对比
- python matplotlib使用ax绘图