golang学习笔记一:基础篇

运行

1
2
$ go run hello.go
Hello, World!
1
2
3
4
5
$ go build hello.go 
$ ls
hello hello.go
$ ./hello
Hello, World!

注释

注释和C++一样,都是/**/ 或者 //,可以用go doc指令来查看代码中的注释,比如我们想查找parse相关的内容:

1
$ go doc -all regexp | grep -i parse

变量

变量声明

一般形式

1
var identifier type
1
var identifier1, identifier2 type

根据值自动判断类型

1
var v_name = value

用:=替代var

用:=声明的变量可以被多次声明

1
v_name := value

初始化

可以在创建的时候初始化值:

1
var a int = 0

不初始化就默认零:

  • 数值类型(包括complex64/128)为 0

  • 布尔类型为 false

  • 字符串为 “”(空字符串)

  • 以下几种类型为 nil

    1
    2
    3
    4
    5
    6
    7
    >   var a *int
    > var a []int
    > var a map[string] int
    > var a chan int
    > var a func(string) int
    > var a error // error 是接口
    >

new 和 make

Go语言中的 new 和 make 主要区别如下:

  • make 只能用来分配及初始化类型为 slice、map、chan 的数据。new 可以分配任意类型的数据;
  • new 分配返回的是指针,即类型 *Type。make 返回引用,即 Type;
  • new 分配的空间被清零。make 分配空间后,会进行初始化;

包引入

fmt

个人理解:和iostream类似,都是格式化I/O的包,简单格式:

Printf

就是个输出,用法和C的printf还有python的format有着微妙的相似性,都是第一个变量定义输出格式,后面填入输出变量。

  1. %v 占位符可以打印任何 Go 的值,“加号”标记(%+v)会添加字段名
  2. %T 可以打印出变量的类型
  3. %#v 相应值的Go语法表示
  4. %% 字母上的百分号,并非值的占位符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
"fmt"
)

type Sample struct {
a int
str string
}

func main() {
s := Sample{a: 1, str: "hello"}

fmt.Printf("%v\n", s) //{1, hello}
fmt.Printf("%+v\n", s) //{a:1, str:hello}
fmt.Printf("%#v\n", s) //main.Sample{a:1, str:"hello"}
fmt.Printf("%T\n", s) // main.Sample
fmt.Printf("%%\n", s.a) // % %!(EXTRA int=1)
}

(抄一个例子),其他普通的%d什么的就懒得写了。

其他特殊用法:

指定宽度

1
2
3
fmt.Printf("%10d\n", 353)  // will print "       353"
//将宽度指定为 * 来将宽度当作 Printf 的参数:
fmt.Printf("%*d\n", 10, 353) // will print " 353"

多次引用一个变量

1
2
// 如果你在一个格式化的字符串中多次引用一个变量,你可以使用 %[n],其中 n 是你的参数索引(位置,从 1 开始)
fmt.Printf("The price of %[1]s was $%[2]d. $%[2]d! imagine that.\n", "carrot", 23)

Sprintf

Sprintf 则格式化并返回一个字 符串而不带任何输出

1
s := fmt.Sprintf("是字符串 %s ","string")

Fprintf

和Printf的区别:格式化并输出到指定的地方

1
fmt.Fprintf(os.Stdout, "%s\n", "hello world!")

这样就是和Printf一样从os.stdout输出,看下面一个例子:

1
2
3
4
buf := bufio.NewWriter(os.Stdout)
// and now so does buf.
fmt.Fprintf(buf, "%s\n", "hello world! - buffered")
buf.Flush()

就变成了把输出扔进队列里。

Println

输出但是无法格式化。

自定义包

抄博客时间:

  • 文件名与包名没有直接关系,不一定要将文件名与包名定成同一个。
  • 文件夹名与包名没有直接关系,并非需要一致。
  • 同一个文件夹下的文件只能有一个包名,否则编译报错。

文件结构:

1
2
3
4
5
6
Test
--helloworld.go

myMath
--myMath1.go
--myMath2.go

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// helloworld.go
package main

import (
"fmt"
"./myMath"
)

func main(){
fmt.Println("Hello World!")
fmt.Println(mathClass.Add(1,1))
fmt.Println(mathClass.Sub(1,1))
}
// myMath1.go
package mathClass
func Add(x,y int) int {
return x + y
}
// myMath2.go
package mathClass
func Sub(x,y int) int {
return x - y
}

常量

常量,也就是在运行时不会被修改的量,常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。常量可以用len(), cap(), unsafe.Sizeof()函数计算表达式的值。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

itoa

iota,特殊常量,可以认为是一个可以被编译器修改的常量。const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1;所以 a=0, b=1, c=2 可以简写为如下形式:

1
2
3
4
5
const (
a = iota
b
c
)

继续抄例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import "fmt"

func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}

运算符

go的运算符倒是和C的没什么太大区别,要用自己查。

语句

整体来讲和常用的语言没什么太大区别,说点不一样的吧

if

Go 对 if 语句做了稍微修改,支持在条件语句被求值之前先进行初始化:

1
2
3
if err := process(); err != nil {
return err
}

这也是一种非常常见的 go 的编写方式,虽然 err 不能在 if 语句之外使用,但他可以在任何 else if 或者 else 之内使用。

这样的写法和下面的写法相比要简介方便非常非常多:

1
2
3
4
5
6
7
8
9
10
f, err := os.Open(name)
if err != nil {
return err
}
d, err := f.Stat()
if err != nil {
f.Close()
return err
}
codeUsing(f, d)

for

go语言没有while,也是for。。。

1
for condition { }

函数

格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func function_name( [parameter list] ) [return_types] {
函数体
}
// example
/* 函数返回两个数的最大值 */
func max(num1, num2 int) int {
/* 声明局部变量 */
var result int

if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}

返回值

可以返回多个值,默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

除了普通的返回值外,当给返回值命名时,它会被直接初始化,并且绑定给一个返回值,这样在函数内部使用的时候可以直接把它当作参数来用。

1
2
3
4
5
6
7
8
9
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:]
}
return
}

返回地址问题:

因为go在执行返回的时候会先给返回内容分配地址,所以返回引用并不会有问题,也就是:return &f

数组

还是和C一样,go语言初始化数组时需要申明数组的长度:

1
2
3
4
5
var 数组变量名 [元素数量]Type
// 例子
var a [3]int // 定义三个整数的数组
// “...”省略号,则表示数组的长度是根据初始化值的个数来计算
q := [...]int{1, 2, 3}

但是9102年了还用这么死板的语言肯定是不行的,所以出现了切片:

切片

切片(slice)是建立在数组之上的更方便,更灵活,更强大的数据结构。切片并不存储任何元素而只是对现有数组的引用。

切片个人感觉和动态数组差不多,具体的运算逻辑和原因就后面再说,这里只提使用方法。你可以声明一个未指定大小的数组来定义切片:

1
var identifier []type

切片不需要说明长度。

或使用make()函数来创建切片:

1
2
3
4
5
var slice1 []type = make([]type, len)

也可以简写为

slice1 := make([]type, len)

向切片中增加新元素:append(oldslice, newelement)

指针

和C的还是无比相似,累了放弃。

结构和方法

结构的定义方法

1
2
3
4
5
6
type struct_variable_type struct {
member definition
member definition
...
member definition
}

关于结构:(抄的)

  1. 用于定义复杂的数据结构
  2. struct里面可以包含多个字段(属性),字段可以是任意类型
  3. struct类型可以定义方法(注意和函数的区别)
  4. struct类型是值类型
  5. struct类型可以嵌套
  6. Go语言没有class类型,只有struct类型

初始化方法:

  1. 指针类型
    var 变量 = new (结构体名称)
    var 变量 = &结构体名称{}
    var 变量 = &结构体名称{成员A:值,成员B:值}
  2. 值类型
    var 变量= 结构体{成员A:值,成员B:值}
1
2
3
4
5
6
7
8
9
10
11
12
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
var Book1 Books
var Book2 *Books = &Book{}
var Book3 *Books = new(Books)
var struct_pointer *Books = &Book1 //指向book1结构体的指针
}

换个例子(还是抄的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type Student struct {
Name string
Age int
Source int
Next *Student
}

func creat() {
var stu_head = Student{Name: "li", Age: 20, Source: 100, Next: nil}
var stu_head1 = new(Student)
var stu_head3 = &Student{
Name: "wang",
Age: 12,
Next: stu_head1,
}
fmt.Println(stu_head)
fmt.Println(*stu_head1)
fmt.Println(stu_head3.Next)
}
func main() {
creat()
}

方法

方法这种东西总算回到了我们熟悉的OO编程的世界。。。

1
2
3
4
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}

Go 没有面向对象,而我们知道常见的 Java,C++ 等语言中,实现类的方法做法都是编译器隐式的给函数加一个 this 指针,而在 Go 里,这个 this 指针需要明确的申明出来,其实和其它 OO 语言并没有很大的区别。

例:

1
2
3
4
func (c Circle) getArea() float64 {
//c.radius 即为 Circle 类型对象中的属性
return 3.14 * c.radius * c.radius
}

Map

像Python的dict的键值对,但是还不知道内部存储方式,累了怎么用以后再说。

Interface

就是我们知道的interface。。。。但是使用的时候不是用implementation或者是继承,只要在interface里定义了会被继承的类元素就行。

继续抄例子:

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
package main

import (
"fmt"
)

type Phone interface {
call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}

func main() {
var phone Phone //!!!,在具体struct iphone和nokia里是万万不能这么写的,因为new返回的是一个地址,需要赋值给一个指针来指向这个地址。

phone = new(NokiaPhone)
phone.call()

phone = new(IPhone)
phone.call()

}

interface的具体实现详见https://www.jianshu.com/p/70003e0f49d1 (后面有空再研究)