环境搭建
-
下载去官方下最新稳定版本就好https://golang.google.cn/
-
windows直接下一步就好,linux下载压缩包,官方建议下到/usr/local/下面然后解压tar -zvxf
-
linux把环境变量添加到/etc/profile里面,然后source一下就能即刻生效
vim /etc/profile // 在最后一行添加 export GOROOT=/usr/local/go export PATH=$PATH:$GOROOT/bin export GOPATH=/root/go #项目工作目录,这个是自己确认的 // wq保存退出后source一下 source /etc/profile
-
环境变量解释,设置完成后检验,就是cmd下执行go version看看
GOROOT 是go语言安装的位置 GOPATH 是go语言写的源码文件夹,也既是我们平时说的工程项目目录,如果多个;隔开。GOPATH 工作空间是一个目录层次结构,其根目录包含三个子目录: src:包含 Go 源文件,注意:你自己创建依赖的package,也要放到GOPATH 目录下,这样才能够被引用到。 pkg:是用来存编译好的库文件 bin:包含可执行命令 Path 在户变量里面修改,是cmd界面下寻找命令的路径(建议用%GOROOT%\bin;%GOPATH%\bin)
-
go语言的IDE建议用goland
-
注释单行用//,多行 /* 注释内容 */
-
编译是用go build,直接go build是编译当前文件夹(此时文件夹内有且只能有一个属于package main)。或者用go build xxx.go单独编一个文件此时没啥限制
编码规范
-
变量以字母、下划线、数字组成,区分大小写,不能包含标点等特殊符号,必须以字母或者下划线开头。
-
在go语言中,以大写字母开头的变量可以被外部包调用有点像public,小写有点像private
-
标准变量声明格式
var 变量名 变量类型 = 初始值或表达式
-
常量 const 标识符 类型=值,常量不允许被修改,值只能是布尔,数字,浮点,字符串类型。iota是系统中常量的计数器,系统中有1个常量就加1,每一次const都会重新重置为0
var a int = 100 var b string = "abc" var c bool = false var d [3]int 定义一个名为d的数组,数组中元素为整形 var e func() string 定义一个名为e的函数,返回值为字符串类型 在函数内定义我们一般使用 k := 100 这样的缩写方式,只能在函数内这样使用 单行注释// 多行注释/* …… */ 字符串类型可以用+拼接起来
数据类型
- 布尔型
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true - 字符串
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。如果有多行字符需要原样输出用键盘数字1前面的反引号2个。字符串的基本操作函数有strings和strconv
var x string = "abc你是傻逼吗,傻逼傻xxoo逼臭傻逼!" //定义一个变量x
k := x[0]
//定义一个变量k,是x变量的第一个字符,如果是[6]代表第7个字节,索引从0开始
fmt.Println(k) //此时K是97,类型是uint
fmt.Println(string(k)) //此时打印a
l := x[:3]
fmt.Println(l) //此时打印abc
可以看出字符串截取的时候如果都是英文,截取出来的单个字符是数字需要转换,多个字符
直接是字符串,当有中文和英文混杂时,一般先把字符串转换成[]rune(x)
var flag = 0
func main() {
m := []rune(x) //此时m是一个类型为rune的切片
for _,arg := range m{
if string(arg)=="傻" {
flag ++
}
}
fmt.Println(flag)
}
另外strconv是字符串与其他类型转换的函数,具体见文档
strconv.FormatInt() 格式化int,转为string
strconv.ParseaInt() 分析字符串,转为int
- 数字类
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码,int有int8(-128-127),int16,int32,int64等等,uint是没有符号的 - 指针类型(Pointer)
形象点就是指向存储数据内存空间的那个针头(指针就是地址),go语言当中这个先简单了解下就好了
x := 18
fmt.Println(&x) //&n就是n这个变量的内存地址的意思
此时&n类型*int,*是指针,int表示这个指针是int类型数据的指针
p := &x
此时p是指针类型,值是指向x的内存地址,而&p是P这个变量本身的内存地址
在指针类型前面加上 * (前缀)来获取指针所指向的内容
-
数组类型(array),数组是一组相同类型数据的集合,长度已经被固定,元素可以被修改。这种类型可以是任意的原始类型例如整形、字符串或者自定义类型。
var a [4] int 定义了一个数组名字为a 长度是4,它的下标0-3 也可以这样定义 var b = [4]int{1,2,3,4} 直接赋值了 数组的遍历,如下示例,index如果换成_就是把取到的值舍弃掉,没用的话 for index,var := a fmt.Println(index,var) 注意数组是值类型数据,=的时候传递的是数组,并不是引用类型 数组排序是使用自己编写的函数,最经典的是冒泡排序,就是左右值对比 多维数组 arry2 :=[3][4]int({},{},{}} [3]代表3个一维数组,[4]代表每个数组有4个元素,此时len()得到的是3代表 3个一维数组
-
切片类型(slice),切片是个引用类型(指针),内部结构包含地址,大小,容量。数组不能更改元素个数,切片可以无限制的放。
切片的定义是 var s2 []int,跟数组很像,但是一定记住不要在[]写数字 通常情况下,比较常用make函数来创建引用类型数据。 s1 := make([]int,3,4) 创建一个切片,里面存在3个数据,切片长度是4 所以我平时使用m :=make([]string,0,0) 切片要在最后新增数据用append()函数,如下 s1 := []int{1,2,3,4,5} s2 := []int{6,7,8,9,10} s3 := append(s1,s2...) #注意这里是在s1末尾增加数据,由于s2是切片, 增加的是切片里的内容所以用... fmt.Println(s3) 结果[1 2 3 4 5 6 7 8 9 10 copy函数 copy(des,src) 常用来切片的拷贝,将src的数据拷贝到des中,注意角标是一一对应的,如果src 长度大于des,只会修改des中原有长度的值
-
Map 类型(映射)
引用类型,有点像python当中的字典,是无序的。var a map[string]int
定义了一个map变量a,这是常规的定义,没有初始化。初始化使用make,k := make(map[string]int,10)
,加值k[“语文”]=100,k[“数学”]=98。也可以一步到胃,var k = map[string]int{"英语":70,"体育":80}
func main(){ k:=map[string]int{"语文":100,"数学":80,"英语":90} fmt.Printf("k的值是%#v\n",k) value,ok := k["体育"] fmt.Println(value,ok) } # 如果k的里面有英语这个key,把key对应的值赋予value,返回true给OK,这是 一个特殊写法 删除用delete()函数 delete(m,键) for subject ,v= range k{ fmt.Println(subject,v) } # 遍历k 如果要删除map中的键值对就使用内置函数delete函数 delete(map,key) g := make([]map[string]string) 创建一个切片g,切片里面的元素是map类型, key是string,值是string
-
结构体类型(struct)
结构体看Go面向对象 -
接口类型
同上 -
Channel 类型
-
函数类型
格式化输出和输出
fmt.Printf
-
%T 变量的数据类型
-
%v 变量的值
-
%c 变量以对应的Unicode字符输出
-
%b 变量二进制
-
%d 变量十进制
-
%x 变量十六进制,a-f
-
%X 变量十六进制,A-F
-
%.2f 变量浮点表示,精度小数点后2位
fmt.Sprintf(“%b”,a) 变量a转换成二进制,并返回 -
%s 变量字符串不带双引号
-
%q 变量字符串带双引号
-
\n 换行符
var x int var y string fmt.Scanln(&x, &y) fmt.Println(x, y) 格式化输入需要使用&变量,这样是使用变量地址,可以理解为输入2个变量, 把两片地址空间分别存入这个变量
运算符
基本与其他语言差不多,可以看这里菜鸟教程
流程控制
-
if,下面是个多选择语句,需要注意顺序,只会匹配一个if,可以嵌套。嵌套是把嵌套语句写在{}里面,这样两个if变成了与的关系
func Judge() { var score int = 71 if score >= 90 { fmt.Println("成绩优秀") }else if score >= 80 { fmt.Println("成绩良好") }else if score >= 70 { fmt.Println("成绩中等") }else { fmt.Println("考成这B样还好意思问") } }
-
switch,用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上至下逐一测试,直到匹配为止,已经优化过不需要break。注意case 后面的判断值(可以是多个)是否相等(实例1),或者不写如下面(实例2)。如需强制执行下一个分支(仅仅一个)的语句需要在case分支后面
最后一行
加fallthrough(贯穿)。特别注意switch var1与case分支中的var类型必须一致
var score int = 90 switch score { case 90: fmt.Println("你好棒啊") case 80: fmt.Println("不错继续加油") }
var score int = 90 switch { case score >= 90: fmt.Println("你好棒啊") case score >= 80: fmt.Println("不错继续加油") }
-
for,go语言就一种循环下图实例100以内3倍数的相加的和
func main() { result := 0 for i:=0;i<=100;i++{ if i%3 == 0{ result +=i } } fmt.Println(result) } i:=0 初始化参数,这样i的可用域只在循环体内,i<=100判断,注意这里只能是布尔类型, i++是执行完表达式后初始参数的变化,当循环省略表达式2的判断,就是死循环,一直真
//遍历字符串 func main() { str := "123ABCabc" for i,value := rang str { fmt.Println(i,value) } }
for i:=1;i<=9;i++ { for j:=1;j<=9;j++ { if j<9 { fmt.Print("o ") }else { fmt.Println("o") } } } for i:=1;i<=9;i++ { for j:=1;j<=i;j++ { if j<i { fmt.Print("o ") }else { fmt.Println("o") } } }
-
break,continue,goto,go语言中有这些控制语句,break跳出当前循环体,continue跳过本次循环,goto跳到指定位置。比如loop: 后面goto loop
生成随机数
Go语言标准库中关于随机函数提供了两种包,分别是“math/rand”和“crypto/rand”
标准库学习指南中文版 https://studygolang.com/pkgdoc
常用方式
const layout = "2006-01-02 15:04:05"
const layout = "2006/01/02 15:04:05" 时间必须写这个是为了纪念go语言诞生
a := time.Now().Format(layout)
b := time.Now().Unix()
c := rand.Intn(100)
此时a打印出来的当前时间就是按这个格式,layout是个设计模板的意思
b是int64的时间戳
rand.Intn(n) 是一个取值0到n-1的随机整数
比如5-49之间的随机数就是rand.Intn(45)+3
注意当种子数相同时,随机模块前K次取出来的值,与下一次程序运行前K次取出来的值是相同的,
so我们常用时间戳来设置种子数
rand.Seed(time.Now().Unix())
func main() {
rand.Seed(time.Now().Unix())
for i := 0; i < 10; i++ {
g := rand.Intn(100)
fmt.Println(g)
}
}
time.sleep() 程序休眠,()当中是纳秒
函数
go语言中函数写法如下,可以有多个返回值,正常参数是a int,b string,如果相同可以简写,同理返回值也是。这里注意当值类型的变量传入函数当参数时,是把变量的值复制一份给函数,并不会修改原来的变量值。引用类型有:切片,map,接口,channel,func,指针
func xxoo(a,b int) (c,d int) {
c = a + b
d = a*b
return c,d
}
- 当需要不固定的参数时候用…,如
func xxoo(a ... int) (c,d int)
这时候a ...
就是0个或者多个int类型参数,a ...
是一个切片,注意可变参数必须放在参数最后一个位置
func xxoo(a ... int) int { //累加
sub := 0
for _,arg := range a{
sub += arg
}
return sub
}
-
函数可以当做参数传给另外一个函数
-
匿名函数
m := func(){} #建立了一个匿名函数赋值给m变量 m() #调用
-
闭包
定义了一个函数,函数内部有嵌套一个匿名函数。这个函数的返回值是一个函数,返回的匿名函数又调用了外层函数的变量func sayword(name string) func(){ return func(){ fmt.Printf("%v你好!",name) } } func main(){ k := sayword("蔡徐坤") k() }
内置函数
- append
用法k := append(k,1)
,加入k是切片,就是在原来切片末尾添加1这个元素 - copy
由于切片是内存指向数据,如果使用普通赋值,会把两个切片都指向同一片内存空间,如果要复制使用copy函数,copy(b,a)
,将a的元素拷贝给b - make
由于切片的容量一般没有限制,为了程序的快速运行,一般是在切片定义的时候想好容量大小,后续就不用再继续扩容,通常做法使用make函数,k = make([]int,5,10)
定义一个切片k元素是整形,长度是5,容量是10
结构体
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,是值类型。有点像其他语言中的类,两个结构体进行==比较,要比对里面的所有字段值
定义封装结构体
type Person struct {
name string
age int
hobby []string
}
需要使用的地方就能var p Person 申明一个结构体,然后一步步具体实例p.name p.age等
其他还有以下两种使用方式
var p = Person{ //第一种一步到位
age:18,
name:"xxoo",
}
func newPerson(name string,age int,hobby []string) Person{//第二种定义构造
return Person{
name: name,
age: age,
hobby: hobby,
}
}
第2种新建一个构造函数newperson(约定俗成使用new打头就是构造函数),在需要的地
方直接调用函数,接收返回值就可以。如果结构体里面内容比较多,通常使用*person
作为返回值类型,而返回值写return &person 这样操作的都是指针,这个较常用
方法是作用于特定类型的函数,不是谁都能调用的,如下,(p person)定义接收者为
person类型,前面的p为实例,约定用结构体的第一个小写字母
func (p Person) wang(){
fmt.Println("汪汪汪")
}
p.wang()
func (p *People) run () {
fmt.Printf("%v正在跑步",p.name)
p.weight--
}
使用指针时是为了更高效,因为指针较小,而且可以指向自己的属性同时修改
匿名属性,把属性名称去掉,不允许两个同类型的匿名属性
type Person struct {
string
int
}
封装
go语言当中也是有面向对象编程的,就是我们的结构体。由于封装需要被其他包调用所以常大写开头,而如果结构体中的属性是小写的话,也是不允许外部直接访问的,需要通过特定方法调用私有属性
type People struct {
name string
age int
}
定义了公有Setname方法,可以外部直接调用,由于要修改原name属性必须使用指针如下
func (p *People) Setname(newname string) {
p.name = newname
}
func (p *People) Getname() {
return p.name
}
继承
类型于其他编程语言的使用方式,还使用匿名属性的特点
type People struct {
name string
age int
}
type Student struct {
classroom string
People
}
s := Student{classroom:"三年一班",People:People{name:"奇多",age:18}}
fmt.Println(s.name)
这里学生结构体逻辑上,是从属于人这个结构体,这里结构体Student里People使用了匿名
属性的特点,全写是peo People,省略属性名字,就直接People了。调用结果就是"奇多"
同理属于People的方法,s也能直接调用了
接口
接口是一个类型(心里反复念无数遍),它把所有的具有共性的方法定义在一起,只能有名称,参数,返回值。任何其他类型只要实现了这些所有方法就是实现了这个接口,约定俗成在名称后面加er。
type Phoner interface { //定义了一个接口Phoner
call()
}
//定义了一个结构体NokiaPhone
type NokiaPhone struct {
name string
price float64
}
//定义了一个结构体IPhone
type IPhone struct {}
func (nokiaPhone NokiaPhone) call() {
fmt.Println("I am Nokia, I can call you!")
}
func (iPhone IPhone) call() {
fmt.Println("I am iPhone, I can call you!")
}
var phoner Phoner = NokiaPhone{name:"NokiaN7",price:3988.88,}
phoner.call()
当将NokiaPhone赋予变量phoner和当将iPhone赋予变量phoner时,调用相同
的方法,产生不同的结果
或者
func (x Phoner){
x.call()
}
很明显接口限制了类型实现的方法,当实现方法的是值类型参数时,可以把结构
体或者结构体指针赋给方法变量。当实现方法的是指针类型参数接收者时,只能
传结构体指针给接口变量
接口的嵌套,语法参照结构体,一模一样
空接口
type Xxooer interface{} //定义空接口
由于空接口,所有类型的数据都可以属于这个接口类型特别是在函数的参数传递
func aabb (a interface{}){
此时a就可以是任何类型数据传入函数了
}