包、变量和函数

一、举个例子

现在我们来建立一个完整的程序main.go

// Golang程序入口的包名必须为 main
package main // import "golang" // 导入其他地方的包,包通过 go mod 机制寻找
import (
"fmt"
"golang/diy"
) // init函数在main函数之前执行
func init() {
// 声明并初始化三个值
var i, j, k = 1, 2, 3
// 使用格式化包打印
fmt.Println("init hello world")
fmt.Println(i, j, k)
} // 函数,两个数相加
func sum(a, b int64) int64 {
return a + b
} // 程序入口必须为 main 函数
func main() {
// 未使用的变量,不允许声明
//cannot := 6 fmt.Println("hello world") // 定义基本数据类型
p := true // bool
a := 3 // int
b := 6.0 // float64
c := "hi" // string
d := [3]string{"1", "2", "3"} // array,基本不用到
e := []int64{1, 2, 3} // slice
f := map[string]int64{"a": 3, "b": 4} // map
fmt.Printf("type:%T:%v\n", p, p)
fmt.Printf("type:%T:%v\n", a, a)
fmt.Printf("type:%T:%v\n", b, b)
fmt.Printf("type:%T:%v\n", c, c)
fmt.Printf("type:%T:%v\n", d, d)
fmt.Printf("type:%T:%v\n", e, e)
fmt.Printf("type:%T:%v\n", f, f) // 切片放值
e[0] = 9
// 切片增加值
e = append(e, 3) // 增加map键值
f["f"] = 5 // 查找map键值
v, ok := f["f"]
fmt.Println(v, ok)
v, ok = f["ff"]
fmt.Println(v, ok) // 判断语句
if a > 0 {
fmt.Println("a>0")
} else {
fmt.Println("a<=0")
} // 死循环语句
a = 0
for {
if a >= 10 {
fmt.Println("out")
// 退出循环
break
} a = a + 1
if a > 5 {
continue
} else {
fmt.Println(a)
} } // 循环语句
for i := 9; i <= 10; i++ {
fmt.Printf("i=%d\n", i)
} // 循环切片
for k, v := range e {
fmt.Println(k, v)
} // 循环map
for k, v := range f {
fmt.Println(k, v)
} // 定义 int64 变量
var h, i int64 = 4, 6 // 使用函数
sum := sum(h, i)
fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum) // 新建结构体,值
g := diy.Diy{
A: 2,
//b: 4.0, // 小写成员不能导出
} // 打印类型,值
fmt.Printf("type:%T:%v\n", g, g) // 小写方法不能导出
//g.set(1,1)
g.Set(1, 1)
fmt.Printf("type:%T:%v\n", g, g) // 结构体值变化 g.Set2(3, 3)
fmt.Printf("type:%T:%v\n", g, g) // 结构体值未变化 // 新建结构体,引用
k := &diy.Diy{
A: 2,
}
fmt.Printf("type:%T:%v\n", k, k)
k.Set(1, 1)
fmt.Printf("type:%T:%v\n", k, k) // 结构体值变化
k.Set2(3, 3)
fmt.Printf("type:%T:%v\n", k, k) // 结构体值未变化 // 新建结构体,引用
m := new(diy.Diy)
m.A = 2
fmt.Printf("type:%T:%v\n", m, m) s := make([]int64, 5)
s1 := make([]int64, 0, 5)
m1 := make(map[string]int64, 5)
m2 := make(map[string]int64)
fmt.Printf("%#v,cap:%#v,len:%#v\n", s, cap(s), len(s))
fmt.Printf("%#v,cap:%#v,len:%#v\n", s1, cap(s1), len(s1))
fmt.Printf("%#v,len:%#v\n", m1, len(m1))
fmt.Printf("%#v,len:%#v\n", m2, len(m2)) var ll []int64
fmt.Printf("%#v\n", ll)
ll = append(ll, 1)
fmt.Printf("%#v\n", ll)
ll = append(ll, 2, 3, 4, 5, 6)
fmt.Printf("%#v\n", ll)
ll = append(ll, []int64{7, 8, 9}...)
fmt.Printf("%#v\n", ll) fmt.Println(ll[0:2])
fmt.Println(ll[:2])
fmt.Println(ll[0:])
fmt.Println(ll[:])
}

在相同目录下新建diy文件夹,文件下新建一个diy.go文件(名字任取):

// 包名
package diy // 结构体
type Diy struct {
A int64 // 大写导出成员
b float64 // 小写不可以导出
} // 引用结构体的方法,引用传递,会改变原有结构体的值
func (diy *Diy) Set(a int64, b float64) {
diy.A = a
diy.b = b
return
} // 值结构体的方法,值传递,不会改变原有结构体的值
func (diy Diy) Set2(a int64, b float64) {
diy.A = a
diy.b = b
return
} // 小写方法,不能导出
func (diy Diy) set(a int64, b float64) {
diy.A = a
diy.b = b
return
} // 小写函数,不能导出,只能在同一包下使用
func sum(a, b int64) int64 {
return a + b
}

进入文件所在目录,打开命令行终端,执行:

go mod init
go run main.go

会显示一些打印结果:

init hello world
1 2 3
hello world
type:bool:true
type:int:3
type:float64:6
type:string:hi
type:[3]string:[1 2 3]
type:[]int64:[1 2 3]
type:map[string]int64:map[a:3 b:4]
5 true
0 false
a>0
1
2
3
4
5
out
i=9
i=10
0 9
1 2
2 3
3 3
a 3
b 4
f 5
sum(h+i),h=4,i=6,10
type:diy.Diy:{2 0}
type:diy.Diy:{1 1}
type:diy.Diy:{1 1}
type:*diy.Diy:&{2 0}
type:*diy.Diy:&{1 1}
type:*diy.Diy:&{1 1}
type:*diy.Diy:&{2 0}
[]int64{0, 0, 0, 0, 0},cap:5,len:5
[]int64{},cap:5,len:0
map[string]int64{},len:0
map[string]int64{},len:0
[]int64(nil)
[]int64{1}
[]int64{1, 2, 3, 4, 5, 6}
[]int64{1, 2, 3, 4, 5, 6, 7, 8, 9}
[1 2]
[1 2]
[1 2 3 4 5 6 7 8 9]
[1 2 3 4 5 6 7 8 9]

我们看到Golang语言只有小括号和大括号,不需要使用逗号来分隔代码,只有一种循环for

接下来我们会分析这个例子。

二、工程管理:包机制

每一个大型的软件工程项目,都需要进行工程管理。工程管理的一个环节就是代码层次的管理。

包,也称为库,如代码的一个包,代码的一个库,英文:Library或者Package。比如,我们常常听到某程序员说:嘿,X哥,我知道Github上有一个更好用的数据加密库,几千颗星呢。

在高级编程语言层次,也就是代码本身,各种语言发明了包(package)机制来更好的管理代码,将代码按功能分类归属于不同的包。

Golang语言目前的包管理新机制叫go mod

我们的项目结构是:

├── diy
│ └── diy.go
└── main.go

每一个*.go源码文件,必须属于一个包,假设包名叫diy,在代码最顶端必须有package diy,在此之前不能有其他代码片段,如diy/diy.go文件中:

// 包名
package diy // 结构体
type Diy struct {
A int64 // 大写导出成员
b float64 // 小写不可以导出
}

作为执行入口的源码,则强制包名必须为main,入口函数为func main(),如main.go文件中:

// Golang程序入口的包名必须为 main
package main // import "golang" // 导入其他地方的包,包通过 go mod 机制寻找
import (
"fmt"
"golang/diy"
)

在入口文件main.go文件夹下执行以下命令:

go mod int

该命令会解析main.go文件的第一行package main // import "golang",注意注释//后面的import "golang",会生成go.mod文件:

module golang

go 1.13

Golang编译器会将这个项目认为是包golang,这是整个项目最上层的包,而底下的文件夹diy作为package diy,包名全路径就是golang/diy

接着,main.go为了导入包,使用import ()

// 导入其他地方的包,包通过 go mod 机制寻找
import (
"fmt"
"golang/diy"
)

可以看到导入了官方的包fmt和我们自已定义的包golang/diy,官方的包会自动寻找到,不需要任何额外处理,而自己的包会在当前项目往下找。

在包golang/diy中,我们定义了一个结构体和函数:

// 结构体
type Diy struct {
A int64 // 大写导出成员
b float64 // 小写不可以导出
} // 小写函数,不能导出,只能在同一包下使用
func sum(a, b int64) int64 {
return a + b
}

对于包中小写的函数或者结构体中小写的字段,不能导出,其他包不能使用它,Golang用它实现了私有或公有控制,毕竟有些包的内容我们不想在其他包中被使用,类似Javaprivate关键字。

结构体和函数会在后面的章节介绍,现在只需知道只有大写字母开头的结构体或函数,才能在其他包被人引用。

最后,Golang的程序入口统一在包main中的main函数,执行程序时是从这里开始的:

package main
import "fmt" // init函数在main函数之前执行
func init() {
// 声明并初始化三个值
var i, j, k = 1, 2, 3
// 使用格式化包打印
fmt.Println("init hello world")
fmt.Println(i, j, k)
} // 程序入口必须为 main 函数
func main() {
}

有个必须注意的事情是函数init()会在每个包被导入之前执行,如果导入了多个包,那么会根据包导入的顺序先后执行init(),再回到执行函数main()

三、变量

Golang语言可以先声明变量,再赋值,也可以直接创建一个带值的变量。如:

// 声明并初始化三个值
var i, j, k = 1, 2, 3 // 声明后再赋值
var i int64
i = 3 // 直接赋值,创建一个新的变量
j := 5

可以看到var i int64,数据类型是在变量的后面而不是前面,这是Golang语言与其他语言最大的区别之一。

同时,作为一门静态语言,Golang在编译前还会检查哪些变量和包未被引用,强制禁止游离的变量和包,从而避免某些人类低级错误。如:

package main

func main(){
a := 2
}

如果执行将会报错:

go run main.go

./main.go:26:2: cannot declared and not used

提示声明变量未使用,这是Golang语言与其他语言最大的区别之一。

变量定义后,如果没有赋值,那么存在默认值。我们也可以定义常量,只需加关键字const,如:

    const s  = 2

常量一旦定义就不能修改。

四、基本数据类型

我们再来看看基本的数据类型有那些:

    // 定义基本数据类型
p := true // bool
a := 3 // int
b := 6.0 // float64
c := "hi" // string
d := [3]string{"1", "2", "3"} // array,基本不用到
e := []int64{1, 2, 3} // slice
f := map[string]int64{"a": 3, "b": 4} // map
fmt.Printf("type:%T:%v\n", p, p)
fmt.Printf("type:%T:%v\n", a, a)
fmt.Printf("type:%T:%v\n", b, b)
fmt.Printf("type:%T:%v\n", c, c)
fmt.Printf("type:%T:%v\n", d, d)
fmt.Printf("type:%T:%v\n", e, e)
fmt.Printf("type:%T:%v\n", f, f)

输出:

type:bool:true
type:int:3
type:float64:6
type:string:hi
type:[3]string:[1 2 3]
type:[]int64:[1 2 3]
type:map[string]int64:map[a:3 b:4]

数据类型基本有整数,浮点数,字符串,布尔值,数组,切片(slice) 和 字典(map) 。

  1. 布尔值:bool
  2. 整数:int(默认类型,一般视操作系统位数=int32或int64),int32int64
  3. 浮点数:float32float64(默认类型,更大的精度)
  4. 字符:string
  5. 数组,切片(可变长数组),字典(键值对结构)。

没声明具体变量类型的时候,会自动识别类型,把整数认为是int类型,把带小数点的认为是float64类型,如:

    a := 3                                // int
b := 6.0 // float64

所以当你需要使用确切的int64float32类型时,你需要这么做:

    var a int64 = 3
var b float32 = 6.0

Golang有数组类型的提供,但是一般不使用,因为数组不可变长,当你把数组大小定义好了,就再也无法变更大小。所以Golang语言造出了可变长数组:切片(slice),将数组的容量大小去掉就变成了切片。切片,可以像切东西一样。自动调整大小,可以切一部分,或者把两部分拼起来。

    d := [3]string{"1", "2", "3"}         // array,基本不用到
e := []int64{1, 2, 3} // slice

切片可以像数组一样按下标取值,放值,也可以追加值:

    // 切片放值
e[0] = 9
// 切片增加值
e = append(e, 3)

切片追加一个值3进去需要使用append关键字,然后将结果再赋给自己本身,这是Golang语言与其他语言最大的区别之一,实际切片底层有个固定大小的数组,当数组容量不够时会生成一个新的更大的数组。

同时,因为日常开发中,我们经常将两个数据进行映射,类似于查字典一样,先查字母,再翻页。所以字典map开发使用频率极高,所以Golang自动提供了这一数据类型,这是Golang语言与其他语言最大的区别之一。

字典存储了一对对的键值:

    // 增加map键值
f["f"] = 5 // 查找map键值
v, ok := f["f"]
fmt.Println(v, ok)
v, ok = f["ff"]
fmt.Println(v, ok)

结构如map[string]int64表示键为字符串string,值为整数int64,然后你可以将f = 5这种关系进行绑定,需要时可以拿出键f对应的值。

五、slice 和 map 的特殊说明

键值结构字典:map使用前必须初始化,如:

     m := map[string]int64{}
m1 = make(map[string]int64)

如果不对字典进行初始化,作为引用类型,它是一个nil空引用,你使用空引用,往字典里添加键值对,将会报错。

而切片结构slice不需要初始化,因为添加值时是使用append操作,内部会自动初始化,如:

    var ll []int64
fmt.Printf("%#v\n", ll)
ll = append(ll, 1)
fmt.Printf("%#v\n", ll)

打印:

[]int64(nil)
[]int64{1}

同时切片有以下特征:

    ll = append(ll, 2, 3, 4, 5, 6)
fmt.Printf("%#v\n", ll)
ll = append(ll, []int64{7, 8, 9}...)
fmt.Printf("%#v\n", ll) fmt.Println(ll[0:2])
fmt.Println(ll[:2])
fmt.Println(ll[0:])
fmt.Println(ll[:])

内置语法append可以传入多个值,将多个值追加进切片。并且可以将另外一个切片,如[]int64{7, 8, 9}...,用三个点表示遍历出里面的值,把一个切片中的值追加进另外一个切片。

在切片后面加三个点...表示虚拟的创建若干变量,将切片里面的值赋予这些变量,再将变量传入函数。

我们取切片的值,除了可以通过下标取一个值,也可以取范围:[下标起始:下标截止(不包括取该下标的值)],如[0:2],表示取出下标为0和1的值,总共有两个值,再比如[0:4],表示取出下标为0,1,2,3的值。如果下标取值,下标超出实际容量,将会报错。

如果下标起始等于下标0,那么可以省略,如[:2],如果下标截止省略,如[2:]表示从下标2开始,取后面所有的值。这个表示[:]本身没有作用,它就表示切片本身。

六、函数

我们可以把经常使用的代码片段封装成一个函数,方便复用:

// 函数,两个数相加
func sum(a, b int64) int64 {
return a + b
}

Golang定义函数使用的关键字是func,后面带着函数名sum(a, b int64) int64,表示函数sum传入两个int64整数ab,输出值也是一个int64整数。

使用时:

    // 定义 int64 变量
var h, i int64 = 4, 6 // 使用函数
sum := sum(h, i)
fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum)

输出:

sum(h+i),h=4,i=6,10

将函数外的变量hi传入函数sum作为参数,是一个值拷贝的过程,会拷贝hi的数据到参数ab,这两个变量是函数sum内的局部变量,两个变量相加后返回求和结果。

就算函数里面改了局部变量的值,函数外的变量还是不变的,如:

package main

import "fmt"

func changeTwo(a, b int) {
a = 6
b = 8
} func main() {
a, b := 1, 2
fmt.Println(a, b)
changeTwo(a, b)
fmt.Println(a, b)
}

输出:

1 2
1 2

变量是有作用域的,作用域主要被约束在各级大括号{}里面,所以函数里面的变量和函数体外的变量是没有关系的,互相独立。

我们还可以实现匿名的函数如:

    input := 2

    output := func(num int) int {
num = num * 2
return num
}(input) fmt.Println(output)

打印出:

4

本来函数在外部是这样的:

func A(num int) int {
num = num * 2
return num
}

现在省略了函数名,定义后直接使用:

    output := func(num int) int {
num = num * 2
return num
}(input)

input是匿名函数的输入参数,匿名函数返回的值会赋予output

系列文章入口

我是陈星星,欢迎阅读我亲自写的 数据结构和算法(Golang实现),文章首发于 阅读更友好的GitBook

数据结构和算法(Golang实现)(2)简单入门Golang-包、变量和函数的更多相关文章

  1. 数据结构和算法(Golang实现)(1)简单入门Golang-前言

    数据结构和算法在计算机科学里,有非常重要的地位.此系列文章尝试使用 Golang 编程语言来实现各种数据结构和算法,并且适当进行算法分析. 我们会先简单学习一下Golang,然后进入计算机程序世界的第 ...

  2. 数据结构和算法(Golang实现)(3)简单入门Golang-流程控制语句

    流程控制语句 计算机编程语言中,流程控制语句很重要,可以让机器知道什么时候做什么事,做几次.主要有条件和循环语句. Golang只有一种循环:for,只有一种判断:if,还有一种特殊的switch条件 ...

  3. 数据结构和算法(Golang实现)(4)简单入门Golang-结构体和方法

    结构体和方法 一.值,指针和引用 我们现在有一段程序: package main import "fmt" func main() { // a,b 是一个值 a := 5 b : ...

  4. 数据结构和算法(Golang实现)(5)简单入门Golang-接口

    接口 在Golang世界中,有一种叫interface的东西,很是神奇. 一.数据类型 interface{} 如果你事前并不知道变量是哪种数据类型,不知道它是整数还是字符串,但是你还是想要使用它. ...

  5. 数据结构和算法(Golang实现)(6)简单入门Golang-并发、协程和信道

    并发.协程和信道 Golang语言提供了go关键字,以及名为chan的数据类型,以及一些标准库的并发锁等,我们将会简单介绍一下并发的一些概念,然后学习这些Golang特征知识. 一.并发介绍 我们写程 ...

  6. 数据结构和算法(Golang实现)(7)简单入门Golang-标准库

    使用标准库 一.避免重复造轮子 官方提供了很多库给我们用,是封装好的轮子,比如包fmt,我们多次使用它来打印数据. 我们可以查看到其里面的实现: package fmt func Println(a ...

  7. CQRS简单入门(Golang)

    一.简单入门之入门 CQRS/ES和领域驱动设计更搭,故整体分层沿用经典的DDD四层.其实要实现的功能概要很简单,如下图. 基础框架选择了https://github.com/looplab/even ...

  8. Java数据结构和算法之数组与简单排序

    一.数组于简单排序 数组 数组(array)是相同类型变量的集合,可以使用共同的名字引用它.数组可被定义为任何类型,可以是一维或多维.数组中的一个特别要素是通过下标来访问它.数组提供了一种将有联系的信 ...

  9. 《Java数据结构与算法》笔记-CH3简单排序

    class ArrayBub { private long[] arr; private int nElement; public ArrayBub(int size) { arr = new lon ...

随机推荐

  1. Js中的For循环详解

    大家好,我是逆战班的一员,今天给大家讲解一下Js循环中的For循环. For循环是JS循环中一个非常重要的部分. 我们先讲一下for循环的作用: For循环用在需要重复执行的某些代码,比如从1打印到1 ...

  2. shell脚本基础-语法

    一 变量 [root@T_FOOT-Home2-ZZZ01 ~]# a=hello [root@T_FOOT-Home2-ZZZ01 ~]# echo $a hello [root@T_FOOT-Ho ...

  3. 全国职业技能大赛信息安全管理与评估-MySQL弱口令利用

    MySQL读文件 #coding=utf-8 import MySQLdb host = '172.16.1.' for i in range(129,131): tag = host+str(i) ...

  4. Java流中的map算子和flatMap算子的区别

    map算子和flatMap算子 map和flatMap都是映射(转换),那么他们之间究竟有什么区别呢? 1.我们先简单了解下map算子: @org.junit.Test public void tes ...

  5. Mysql性能优化:为什么要用覆盖索引?

    导读 相信读者看过很多MYSQL索引优化的文章,其中有很多优化的方法,比如最佳左前缀,覆盖索引等方法,但是你真正理解为什么要使用最佳左前缀,为什么使用覆盖索引会提升查询的效率吗? 本篇文章将从MYSQ ...

  6. 8千字干货教程|java反射精讲

    java反射机制精讲 目录 1. 反射机制的概念 2. 反射的基础Class类 3. 反射的用法 4. 反射的应用示例 作者简介:全栈学习笔记,一个正在努力的人 微信公众号:公众号日更,精彩美文每天推 ...

  7. pat 乙级 1015. 德才论 (25) c++

     http://39.106.25.239 个人网站 欢迎访问 交流 1015. 德才论 (25) 时间限制 200 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Sta ...

  8. 一文带你入门Java Stream流,太强了

    两个星期以前,就有读者强烈要求我写一篇 Java Stream 流的文章,我说市面上不是已经有很多了吗,结果你猜他怎么说:"就想看你写的啊!"你看你看,多么苍白的喜欢啊.那就&qu ...

  9. 【LeetCode】15.三数之和

    题目描述 1. 三数之和 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组. 注意: ...

  10. nginx负载均衡例子

    upstream demo { ip_hash;//客户连接后, 一直用这个IP,直到会话结束,否则,动态程序可能会在换IP后出错 server 192.168.1.1:80 weight=5 | d ...