Go语言入门系列2 基本语法
不用加分号
if 语句没有圆括号
Hello World
|
1
2
3
4
5
6
7
|
package main //声明本文件的package名import"fmt"func main() { fmt.Println("hello world")} |
运行
你可以有两种运行方式,
|
1
2
|
$go run hello.gohello world |
|
1
2
3
4
5
6
7
|
$go build hello.go$lshello hello.go$./hellohello world |
自己的package
你可以使用GOPATH环境变量,或是使用相对路径来import你自己的package。
Go的规约是这样的:
1)在import中,你可以使用相对路径,如 ./或 ../ 来引用你的package
2)如果没有使用相对路径,那么,go会去找$GOPATH/src/目录。
|
1
|
import"./haoel" //import当前目录里haoel子目录里的所有的go文件 |
|
1
|
import"haoel" //import 环境变量 $GOPATH/src/haoel子目录里的所有的go文件 |
fmt输出格式
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package mainimport "fmt"import "math"func main() { fmt.Println("hello world") fmt.Printf("%t\n", 1==2) fmt.Printf("二进制:%b\n", 255) fmt.Printf("八进制:%o\n", 255) fmt.Printf("十六进制:%X\n", 255) fmt.Printf("十进制:%d\n", 255) fmt.Printf("浮点数:%f\n", math.Pi) fmt.Printf("字符串:%s\n", "hello world")} |
也可以使用如\n\t\r这样的和C语言一样的控制字符
变量和常量
变量的声明很像 javascript,使用 var关键字。注意:go是静态类型的语言,下面是代码:
|
1
2
3
4
5
6
7
8
|
//声明初始化一个变量var x int = 100var str string = "hello world"//声明初始化多个变量var i, j, k int = 1, 2, 3//不用指明类型,通过初始化值来推导varb = true//bool型 |
还有一种定义变量的方式(这让我想到了Pascal语言,但完全不一样)
|
1
|
x := 100//等价于 var x int = 100; |
常量很简单,使用const关键字:
|
1
2
|
consts string = "hello world"constpi float32 = 3.1415926 |
数组
直接看代码(注意其中的for语句,和C很相似吧,就是没有括号了)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func main() { var a [5]int 注意这个int紧挨着,没有空格 fmt.Println("array a:", a) a[1] = 10 a[3] = 30 fmt.Println("assign:", a) fmt.Println("len:", len(a)) b := [5]int{1, 2, 3, 4, 5} fmt.Println("init:", b) var c [2][3]int for i := 0; i < 2; i++ { for j :=0; j < 3; j++ { c[i][j] = i + j } } fmt.Println("2d: ", c)} |
运行结果:
|
1
2
3
4
5
|
array a: [0 0 0 0 0]assign: [0 10 0 30 0]len: 5init: [1 2 3 4 5]2d: [[0 1 2] [1 2 3]] |
数组的切片操作
这个很Python了。
|
1
2
3
4
5
6
7
8
9
10
|
a := [5]int{1, 2, 3, 4, 5}b := a[2:4] // a[2] 和 a[3],但不包括a[4]fmt.Println(b)b = a[:4] // 从 a[0]到a[4],但不包括a[4]fmt.Println(b)b = a[2:] // 从 a[2]到a[4]fmt.Println(b) |
分支循环语句
if语句
注意:if 语句没有圆括号,而必需要有花括号
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
if x % 2== 0{ //...}ifx % 2== 0{ //偶数...} else{ //奇数...}if num < 0{ //负数} else if num == 0{ //零} else{ //正数} |
switch 语句
注意:switch语句没有break,还可以使用逗号case多个值
|
1
2
3
4
5
6
7
8
9
10
11
12
|
switch i { case 1: fmt.Println("one") case 2: fmt.Println("two") case 3: fmt.Println("three") case 4,5,6: fmt.Println("four, five, six") default: fmt.Println("invalid value!")} |
for 语句
前面你已见过了,下面再来看看for的三种形式:(注意:Go语言中没有while)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
for i := 0; i<10; i++{ fmt.Println(i)}//精简的for语句 conditioni := 1for i<10 { fmt.Println(i) i++}//死循环的for语句 相当于for(;;)i :=1for { if i>10 { break } i++} |
关于分号
从上面的代码我们可以看到代码里没有分号。
通常Go程序仅在for循环语句中使用分号,如果你在一行中写多个语句,也需要用分号分开。
注意:无论任何时候,你都不应该将一个控制结构((if、for、switch或select)的左大括号放在下一行。如果这样做,将会在大括号的前方插入一个分号,这可能导致出现不想要的结果。
map
map在别的语言里可能叫哈希表或叫dict,下面是和map的相关操作的代码,代码很容易懂
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
func main(){ m := make(map[string]int) //使用make创建一个空的map m["one"] = 1 m["two"] = 2 m["three"] = 3 fmt.Println(m) //输出 map[three:3 two:2 one:1] (顺序在运行时可能不一样) fmt.Println(len(m)) //输出 3 v := m["two"] //从map里取值 fmt.Println(v) // 输出 2 delete(m, "two") fmt.Println(m) //输出 map[three:3 one:1] m1 := map[string]int{"one": 1, "two": 2, "three": 3} fmt.Println(m1) //输出 map[two:2 three:3 one:1] (顺序在运行时可能不一样) for key, val := range m1{ fmt.Printf("%s => %d \n", key, val) /*输出:(顺序在运行时可能不一样) three => 3 one => 1 two => 2*/ }} |
指针
Go语言一样有指针,看代码
|
1
2
3
4
5
6
7
8
9
10
11
12
|
var i int= 1var pInt *int= &ifmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)//输出:i=1 pInt=0xf8400371b0 *pInt=1
*pInt = 2fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) //输出:i=2 pInt=0xf8400371b0 *pInt=2
i = 3fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt)//输出:i=3 pInt=0xf8400371b0 *pInt=3 |
Go具有两个分配内存的机制,分别是内建的函数new和make。他们所做的事不同,所应用到的类型也不同,这可能引起混淆,但规则却很简单:
内存分配
new 是一个分配内存的内建函数,但不同于其他语言中同名的new所作的工作,它只是将内存清零,而不是初始化内存。
new(T)为一个类型为T的新项目分配了值为零的存储空间并返回其地址,也就是一个类型为*T的值。用Go的术语来说,就是它返回了一个指向新分配的类型为T的零值的指针。
make(T, args)函数的目的与new(T)不同。它仅用于创建切片、map和chan(消息管道),并返回类型T(不是*T)的一个被初始化了的(不是零)实例。
这种差别的出现是由于这三种类型实质上是对在使用前必须进行初始化的数据结构的引用。例如,切片是一个具有三项内容的描述符,包括指向数据(在一个数组内部)的指针、长度以及容量,在这三项内容被初始化之前,切片值为nil。对于切片、映射和信道,make初始化了其内部的数据结构并准备了将要使用的值。如:
下面的代码分配了一个整型数组,长度为10,容量为100,并返回前10个数组的切片
|
1
|
make([]int, 10, 100) |
以下示例说明了new和make的不同。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var p *[]int = new([]int) // 为切片结构分配内存;*p == nil;很少使用var v []int = make([]int, 10) // 切片v现在是对一个新的有10个整数的数组的引用// 不必要地使问题复杂化:fmt.Println(p) //输出:&[]*p = make([]int, 10, 10)fmt.Println(p) //输出:&[0 0 0 0 0 0 0 0 0 0]fmt.Println((*p)[2]) //输出: 0// 习惯用法:v := make([]int, 10)fmt.Println(v) //输出:[0 0 0 0 0 0 0 0 0 0] |
函数
反过来声明变量类型和函数返回值
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package mainimport"fmt"func max(a int, b int) int{ if a > b { return a } returnb}func main(){ fmt.Println(max(4, 5))} |
函数返回多个值
Go中很多Package 都会返回两个值,一个是正常值,一个是错误,如下所示:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
package mainimport "fmt"func main(){ v, e := multi_ret("one") fmt.Println(v,e) //输出 1 true v, e = multi_ret("four") fmt.Println(v,e) //输出 0 false //通常的用法(注意分号后有e) ifv, e = multi_ret("four"); e { // 正常返回 }else{ // 出错返回 }}func multi_ret(key string) (int, bool){ m := map[string]int{"one": 1, "two": 2, "three": 3} var err bool var val int val, err = m[key] returnval, err} |
函数不定参数
例子很清楚了,我就不多说了
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func sum(nums ...int) { fmt.Print(nums, " ") //输出如 [1, 2, 3] 之类的数组 total := 0 for_, num := range nums { //要的是值而不是下标 total += num } fmt.Println(total)}func main() { sum(1, 2) sum(1, 2, 3) //传数组 nums := []int{1, 2, 3, 4} sum(nums...)} |
函数闭包
nextNum这个函数返回了一个匿名函数,这个匿名函数记住了nextNum中i+j的值,并改变了i,j的值,于是形成了一个闭包的用法
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func nextNum() func() int{ i,j := 1,1 return func() int{ var tmp = i+j i, j = j, tmp return tmp }}//main函数中是对nextNum的调用,其主要是打出下一个斐波拉契数func main(){ nextNumFunc := nextNum() for i:=0; i<10; i++ { fmt.Println(nextNumFunc()) }} |
函数的递归
和c基本是一样的
|
1
2
3
4
5
6
7
8
9
10
|
func fact(n int) int{ if n == 0 { return 1 } return n * fact(n-1)}func main() { fmt.Println(fact(7))} |
结构体
Go的结构体和C的基本上一样,不过在初始化时有些不一样,Go支持带名字的初始化。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
type Person struct{ name string age int email 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 := &person fmt.Println(pPerson) //输出 &{Tom 30 tom@gmail.com} pPerson.age = 40 person.name = "Jerry" fmt.Println(person) //输出 {Jerry 40 tom@gmail.com}} |
结构体方法
注意:Go语言中没有public, protected, private的关键字,所以,如果你想让一个方法可以被别的包访问的话,你需要把这个方法的第一个字母大写。这是一种约定。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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 := &r fmt.Println("面积: ", rp.area()) fmt.Println("周长: ", rp.perimeter())} |
(varName *type)
接口和多态
接口意味着多态
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
//---------- 接 口 --------//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()) }} |
错误处理 – Error接口
函数错误返回可能是C/C++时最让人纠结的东西的,Go的多值返回可以让我们更容易的返回错误,其可以在返回一个常规的返回值之外,还能轻易地返回一个详细的错误描述。通常情况下,错误的类型是error,它有一个内建的接口。
|
1
2
3
|
type error interface { Error() string} |
还是看个示例吧:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
packagemainimport"fmt"import"errors"//自定义的出错结构type myError struct { arg int errMsg string}//实现Error接口func (e *myError) Error() string { returnfmt.Sprintf("%d - %s", e.arg, e.errMsg)}//两种出错func error_test(arg int) (int, error) { ifarg < 0 { return-1, errors.New("Bad Arguments - negtive!") }elseifarg >256{ return-1, &myError{arg, "Bad Arguments - too large!"} } returnarg*arg, nil}//相关的测试func main() { for_, i := range []int{-1, 4, 1000} { ifr, e := error_test(i); e != nil { fmt.Println("failed:", e) } else{ fmt.Println("success:", r) } }} |
程序运行后输出:
|
1
2
3
|
failed: Bad Arguments - negtive!success: 16failed: 1000 - Bad Arguments - too large! |
错误处理 – Defer
下面的程序对于每一个熟悉C语言的人来说都不陌生(有资源泄露的问题),C++使用RAII来解决这种问题。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) iferr != nil { return } dst, err := os.Create(dstName) iferr != nil { return } written, err = io.Copy(dst, src) dst.Close() src.Close() return} |
Go语言引入了Defer来确保那些被打开的文件能被关闭。如下所示:(这种解决方式还是比较优雅的)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
func CopyFile(dstName, srcName string) (written int64, err error) { src, err := os.Open(srcName) iferr != nil { return } defer src.Close() dst, err := os.Create(dstName) iferr != nil { return } defer dst.Close() returnio.Copy(dst, src)} |
Go的defer语句预设一个函数调用(延期的函数),该调用在函数执行defer返回时立刻运行。该方法显得不同常规,但却是处理上述情况很有效,无论函数怎样返回,都必须进行资源释放。
我们再来看一个defer函数的示例:
|
1
2
3
|
fori := 0; i < 5; i++ { defer fmt.Printf("%d ", i)} |
被延期的函数以后进先出(LIFO)的顺行执行,因此以上代码在返回时将打印4 3 2 1 0。
总之,我个人觉得defer的函数行为有点怪异,我现在还没有完全搞清楚。
错误处理 – Panic/Recover
对于不可恢复的错误,Go提供了一个内建的panic函数,它将创建一个运行时错误并使程序停止(相当暴力)。该函数接收一个任意类型(往往是字符串)作为程序死亡时要打印的东西。当编译器在函数的结尾处检查到一个panic时,就会停止进行常规的return语句检查。
下面的仅仅是一个示例。实际的库函数应避免panic。如果问题可以容忍,最好是让事情继续下去而不是终止整个程序。
|
1
2
3
4
5
6
7
|
var user = os.Getenv("USER")func init() { ifuser == ""{ panic("no value for $USER") }} |
当panic被调用时,它将立即停止当前函数的执行并开始逐级解开函数堆栈,同时运行所有被defer的函数。如果这种解开达到堆栈的顶端,程序就死亡了。但是,也可以使用内建的recover函数来重新获得Go程的控制权并恢复正常的执行。 对recover的调用会通知解开堆栈并返回传递到panic的参量。由于仅在解开期间运行的代码处在被defer的函数之内,recover仅在被延期的函数内部才是有用的。
你可以简单地理解为recover就是用来捕捉Painc的,防止程序一下子就挂掉了。
下面是一个例程,很简单了,不解释了
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
func g(i int) { ifi>1 { fmt.Println("Panic!") panic(fmt.Sprintf("%v", i)) }}func f() { defer func() { ifr := recover(); r != nil { fmt.Println("Recovered in f", r) } }() fori := 0; i < 4; i++ { fmt.Println("Calling g with ", i) g(i) fmt.Println("Returned normally from g.") }}func main() { f() fmt.Println("Returned normally from f.")} |
运行结果如下:(我们可以看到Painc后的for循环就没有往下执行了,但是main的程序还在往下走)
|
1
2
3
4
5
6
7
8
|
Calling g with 0Returned normally from g.Calling g with 1Returned normally from g.Calling g with 2Panic!Recovered inf 2Returned normally from f. |
Go语言入门系列2 基本语法的更多相关文章
- Go语言入门系列(四)之map的使用
本系列前面的文章: Go语言入门系列(一)之Go的安装和使用 Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 1. 声明 map是一种映射,可以将键(key)映射到值(val ...
- Go语言入门系列(五)之指针和结构体的使用
Go语言入门系列前面的文章: Go语言入门系列(二)之基础语法总结 Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 1. 指针 如果你使用过C或C++,那你肯定对指针这个概念 ...
- Go语言入门系列(六)之再探函数
Go语言入门系列前面的文章: Go语言入门系列(三)之数组和切片 Go语言入门系列(四)之map的使用 Go语言入门系列(五)之指针和结构体的使用 在Go语言入门系列(二)之基础语法总结这篇文章中已经 ...
- 【Go语言入门系列】(七)如何使用Go的方法?
[Go语言入门系列]前面的文章: [Go语言入门系列](四)之map的使用 [Go语言入门系列](五)之指针和结构体的使用 [Go语言入门系列](六)之再探函数 本文介绍Go语言的方法的使用. 1. ...
- 【Go语言入门系列】(八)Go语言是不是面向对象语言?
[Go语言入门系列]前面的文章: [Go语言入门系列](五)指针和结构体的使用 [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? 1. Go是面向对象的语言吗? 在[ ...
- 【Go语言入门系列】(九)写这些就是为了搞懂怎么用接口
[Go语言入门系列]前面的文章: [Go语言入门系列](六)再探函数 [Go语言入门系列](七)如何使用Go的方法? [Go语言入门系列](八)Go语言是不是面向对象语言? 1. 引入例子 如果你使用 ...
- 【Go语言入门系列】Go语言工作目录介绍及命令工具的使用
[Go语言入门系列]前面的文章: [保姆级教程]手把手教你进行Go语言环境安装及相关VSCode配置 [Go语言入门系列](八)Go语言是不是面向对象语言? [Go语言入门系列](九)写这些就是为了搞 ...
- go语言入门教程:基本语法之变量声明及注意事项
一.变量的使用 1.1 什么是变量 变量是为存储特定类型的值而提供给内存位置的名称.在go中声明变量有多种语法. 所以变量的本质就是一小块内存,用于存储数据,在程序运行过程中数值可以改变 1.2 声明 ...
- go语言入门教程:基本语法之数据类型
出处:千锋教育go语言教研部 作者:茹姐 一.基本数据类型 以下是go中可用的基本数据类型 1.1 布尔型bool 布尔型的值只可以是常量 true 或者 false.一个简单的例子:var b bo ...
随机推荐
- C#基础视频教程7.2 如何编写简单游戏
前面一小节我们实现了简单的碰撞检测,但是实际上游戏的对象并不是一个标准的矩形(小鸟是一个不规则的物体,其实碰撞的管道也是不规则物体),所以如果真的要做的比较完美,我们自己要写一个方法,能够导入一个图像 ...
- Project has no project.properties file! Edit the project properties to set one.
解决办法: 右击项目,选择android tools-->fix project properties.然后重启eclipse即可.
- vue - 页面跳转
HTML:a 小程序:navigator Vue:router-link 1. router-link => a标签 2. javascript标签跳转页面 2.1 2.2 3. 常用方法之 ...
- Android内存泄漏分析实战
内存泄漏简单介绍 java能够保证当没有引用指向对象的时候,对象会被垃圾回收器回收.与c语言自己申请的内存自己释放相比,java程序猿轻松了非常多.可是并不代表java程序猿不用操心内存泄漏.当jav ...
- 算法笔记_024:字符串的包含(Java)
目录 1 问题描述 2 解决方案 2.1 蛮力轮询法 2.2 素数相乘法 2.3 位运算法 1 问题描述 给定一长字符串A和一短字符串B.请问,如何最快地判断出短字符串B中的所有字符是否都在长字符串A ...
- HBase in Action前三章笔记
近期接触HBase,看了HBase In Action的英文版.開始认为还行,做了些笔记.可是兴许看下去,越来越感觉到实战这本书比較偏使用上的细节,对于HBase的具体设计涉及得很少.把前三章的一些笔 ...
- 【BIRT】交叉报表中出现空值设置为默认值
在使用BIRT做交叉报表的时候,往往会出现有些维度下的值是空值,例如如下报表: 那么我们可以为这些空值给出默认值,例如"-" 具体操作如下: 点击上图的[Cross Tab]后选中 ...
- linux 下处理大文件
.head tail more .先把大文件进行分割 split split 参数: -a, --suffix-length=N 指定输出文件名的后缀,默认为2个 -b, --bytes=SIZE 指 ...
- 动画曲线demo
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xht ...
- 转载【linux】Linux学习之CentOS6.x+7.x---网卡的配置
转载至:http://www.cnblogs.com/smyhvae/p/3932903.html [正文] Linux系统版本:Centos 6.5 Linux系统版本:Centos 7 目的:将c ...