包、变量和函数

一、举个例子

现在我们来建立一个完整的程序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用它实现了私有或公有控制,毕竟有些包的内容我们不想在其他包中被使用,类似Java的private关键字。

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

最后,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),int32,int64。
  3. 浮点数:float32,float64(默认类型,更大的精度)
  4. 字符:string。
  5. 数组,切片(可变长数组),字典(键值对结构)。

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

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

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

    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整数a和b,输出值也是一个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

将函数外的变量h,i传入函数sum作为参数,是一个值拷贝的过程,会拷贝h和i的数据到参数a和b,这两个变量是函数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. No compiler is provided in this environment报错解决方案

  2. 程序员找工作必备 PHP 基础面试题

    1.优化 MYSQL 数据库的方法 (1) 选取最适用的字段属性,尽可能减少定义字段长度,尽量把字段设置 NOT NULL, 例如’省份,性别’, 最好设置为 ENUM (2) 使用连接(JOIN)来 ...

  3. 44. 更改oracle字符集编码american_america.zh16gbk 改为 SIMPLIFIED CHINESE_CHINA.ZHS16GBK

    注册表NLS_LANG值改为SIMPLIFIED CHINESE_CHINA.ZHS16GBK

  4. github的上面的安全工具

    简介 Scanners Box也被称为 scanbox,是一个强大完备的黑客工具箱,它收集了Github上数10种类别的开源扫描器,包括子域名,数据库,中间件和其他模块化设计的扫描器等,但对于一些被大 ...

  5. Mass Spectrometry-Compatible Subcellular Fractionation for Proteomics 质谱兼容的蛋白质组学的亚细胞分离(解读人:王茹凯)

    文献名:Mass Spectrometry-Compatible Subcellular Fractionation for Proteomics(质谱兼容的蛋白质组学的亚细胞分离) 期刊名:Jpor ...

  6. .NET的资源并不限于.resx文件

    为了构建一个轻量级的资源管理框架以满足简单的本地化(Localization)的需求,我试图直接对现有的Resource编程模型进行扩展.虽然最终没能满足我们的需求,但是这两天也算对.NET如何进行资 ...

  7. 数据库安装和基本sql语句

    数据库概念 文件作为数据进行存储,数据格式千差万别 将保存数据的地方统一起来 MYSQL--------->一款应用软件 用来帮你操作文件的 只要是基于网络通信,底层就是socket 服务端 - ...

  8. cmdb客户端采集数据的完善

    file文件自己去拷贝(这里不提供) custom_settings.py import os BASEDIR = os.path.dirname(os.path.dirname(os.path.ab ...

  9. 答应我,不会这些概念,简历不要写 “熟悉” zookeeper

    整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 一口气说出 9种 分布式ID生成方式,面试官有点懵了 面试总被问 ...

  10. 如何删除Python中文本文件的文件内容?

    在python中: open('file.txt', 'w').close() 或者,如果你已经打开了一个文件: f = open('file.txt', 'r+') f.truncate(0) # ...