第一个Go语言程序,先是声明在main包中,然后导入所需要的外部包,这里用到输出函数,fmt包中有输出函数,可以在终端输出结果,要使程序运行得有主函数,就是这里的func main(),与其他语言的主函数起着同样的作用
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
思考:go语言有其自己的输出函数吗,就一定得导入fmt包吗,如果导入fmt包里面包含多少种输出函数呢,它们之间的区别又是什么呢
解决:
问题一:go语言有自己自身的输出函数,print和println,以println为例,fmt包中的println输出方式为输出到标准输出(STDOUT),而go语言自身的输出函数println输出方式为输出到标准错误(STDERR),这个导致了go语言自身的输出函数输出的结果会与预期输出结果的顺序不一致,而fmt包内的就可以达到完全一致,所以我们更多的是会选择导入fmt包
问题二:fmt包中有主要五个输出函数,①.Print:输出到控制台,且不接受任何格式化操作;②.Println:输出到控制台并且换行;③.Printf:格式化输出,只可以打印出格式化的字符串,只可以直接输出字符串类型的变量(不可以直接输出别的类型);④.Sprintf:格式化输出,将格式化字符串输出到指定的字符串中,有返回值可以用一个变量来接受;⑤.Fprintf:格式化输出,将格式化字符串输出到指定的文件设备中,参数多了一个文件指针*File,主要用于文件操作
问题三:Print和Println就只是换行的区别,是一类输出;剩下的就是另一类输出,Printf,Sprintf和Fprintf就是对于格式化字符串的处理不同,第一个就只能打印格式化字符串,第二个则能返回该字符串,第三个就可以输出到文件中;两个大类的区别就是,前者就是直接输出,后者就是格式化输出;
定义变量的方式
方式一 :var/const name (type) = (数值)
var和const是变量和常量的标识,类型可标可不标,类似于自动识别等号后的数据类型,可以用逗号连接相同类型的变量/常量的赋值,不赋值就给默认值
方式二:name := 数值
同样是自动识别后面数值的类型赋值给变量,若是同一个变量重新赋值同一类型的数值,就只能用 "="号,重新赋值不同类型的数值就是和方式二一样用":="
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
const s string = "constant"
const h = 500000000
fmt.Println(s, h)//constant 500000000
go 语言中的数据类型与其他编程语言的数据类型大致是相同的,只是做了部分删改,这里以c++相比为例,go语言的数据类型少了double类型,而为了实现高精度,在float后加位数,比如float64,float32,float16,float8.注意这些位数不是自己可以定义的,一般是根据自己的需要去选择对应的精度,同样的go语言的数据类型也少了long类型,与float同理,int也是有int64,int32,int16,int8,我感觉这使得数据能够更加的精确,而且能够一定程度的提高内存的利用率。
分支结构 go语言中的分支结构有if-else和switch两种
if-else 在判断条件表达式可以不用加括号,加了括号在编译时也会省略,并且可以在if 后加赋值语句类似于for循环中的赋初值,同样是以分号与条件表达式隔离
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
switch go语言中的case不需要break去中断,case下执行完就直接跳出;go语言中switch后面的表达式并不是必须的,省略之后,case和default 也可以起到与if-else一样的作用
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()//可以获取当前时间,需要导入time包
switch {
case t.Hour() < 12://获取当前时间的时钟部分
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
循环结构 go语言中只有for循环一种,与if-else一样不用括号,编译也同样会省略,break和continue还是同样的作用
package main
import "fmt"
func main() {
i := 1
for {
fmt.Println("loop")
break
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
}
思考:为啥go语言舍弃了while循环和do-while循环
解决问题一:既然for循环可以达到自己所有的需求,并且更加的灵活,那又为啥需要while呢,while的功能有限
数组(array)是一种比较常用的数据类型,go语言中对数组的定义的方式与其他语言也有所区别
一维数组的格式 :var name[len] type,或者用 name := [len] type {},数组的len必须有值
var s [100]int
b := [5]int{1, 2, 3, 4, 5}
二维数组的格式:var name[len][] type,或者用name :=[len][] type{{},{},...}
var a [10][20]int //10行20列
c:=[2][3]int{{1,2,3},{4,5,6}}
c:=[...][2]int{{1,2},{3,4},{5,6}}//行数可以不明确,列数一定要有
求数组长度可以用len()方法,注意的是二维数组用len()其实求的是其行数
package main
import "fmt"
func main() {
var a [5]int
fmt.Println("len:", len(a))//5
var s [10][20]int
fmt.Println("len", len[s])//10
}
切片(slice),学过python的应该是对切片很熟悉了,go语言中的切片与python的切片类似,具体来看看其定义方式吧
格式:
1.声明切片 var name[] type
2.make创建切片 var name [] type = make([]type,len,cap) len为切片的长度,cap为切片的容量,cap可以不赋值,不赋值时默认与len相等
3.引用":="来创建切片
4.通过截取数组的一部分来创建切片
package main
import "fmt"
func main(){
//声明
var s1 [] int //空切片,默认初始化为nil
//make创建
var s2 [] int = make([]int,4)
if s2==nil{
fmt.Println("s2是空")
}else{
fmt.Println("s2不是空")
}//s2不是空
a := make([]int, 5)//创建切片,但是不赋值就初始化为0,而不是nil
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
fmt.Println(a[1:5])//和数组一样从0开始,不包含最后一个,结果为 2,3,4,5
//也可以用":="
s4 := []int{}
if s4==nil{
fmt.Println("s4是空")
}else{
fmt.Println("s4不是空")
}//s4不是空
//从数组中直接切片
arr := [5]int{1,2,3,4,5}
var s5 []int
s5 = arr[1:4] //截取arr[1]到arr[3]
fmt.Println(s5)// 2.3.4
}
切片中的常用的方法 len(),cap(),append(),copy()
len()求长度,cap()求容量,append()可以在切片后加数据,也可以将多个切片结合在一起,结果是返回一个新的切片,copy(),根据长度来复制
package main
import "fmt"
func main(){
var a := make([]int,3,5)
fmt.Printf("len=%v cap:=%v",len(a),cap(a))//len:3,cap:5
var b := [] int{4,5,6}
fmt.Printf("b=%v\n",b)//[4,5,6]
c:=append(b,7,8,9)
fmt.Printf("c=%v\n",c)//[4,5,6,7,8,9]
d:=append(c,b)
fmt.Printf("d=%v\n",d)//[4,5,6,7,8,9,4,5,6]
s1 := []int{1,2,3,4}
var s2 := make([]int,2)
var s3 := make([]int,6)
fmt.Printf("s2=%v\n",s2)//s2=[0,0]
fmt.Printf("s3=%v\n",s3)//s3=[0,0,0,0,0,0]
copy(s2,s1)//将s1复制给s2
copy(s3,s1)//将s1复制给s3
fmt.Printf("s2=%v\n",s2)//s2=[1,2] 比s1长度小,只能copy两个值
fmt.Printf("s3=%v\n",s3)//s3=[1,2,3,4,0,0] 比s1长度大,大于s1部分长度没有值来copy,还是原来的值
}
map(映射,字典),这种数据结构类似于我们在数据结构课上学的散列表,同样是key-value,在这里又被称为字段数组或者关联数组
定义方式
1.直接定义:var name map[keytype]valuetype
2.make定义: name := make(map[keytype]valuetype,len),这里的len可以不赋值,缺省,可以通过len()来获取map的长度,但是map没有cap()方法,可以自动扩容
package main
import "fmt"
func main() {
var a map[stirng]int //空map
a = make(map[string]int) //可通过make赋数据空间
a = map[string][int]{"one":1,"two":2} //也可以直接赋初值给空间
fmt.Println(a) // map[one:1 two:2]
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"] //判断map中是否有"unkonw"这个key
fmt.Println(r, ok) // 0 false
delete(m, "one")//删除key为"one"的一对key-value
fmt.Println(m)//map[two:2]
m2 := map[string]int{"one": 1, "two": 2}
fmt.Println(m2)
}
map中常用的方法,delete()方法是删除对应key的key-value对,len()则是返回当前map的长度,还有一种特殊的确定map中是否有这个key的方法,r,ok :=m[key]可以判断,m中是否有这个key,无则将给r赋这个类型的0值,ok赋值false,有则将给r赋值这个key对应的value,ok赋值true
package main
import "fmt"
func main() {
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(len(m)) // 2
delete(m, "one") //删除key为"one"的一对key-value
fmt.Println(m) //map[two:2]
r, ok := m["one"] //判断map中是否有"one"这个key,没有就返回0,false
fmt.Println(r, ok) // 0 false
v, ok := m["two"]
fmt.Println(v,ok) //2 true
}
列表(range),与python中的类似,主要的作用是实现快速遍历,多用于数组,切片,map的快速遍历
格式:for 变量(索引),变量(索引对应的值):=range name
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i, "value:", num)
break
}
}
s := map[string]int{"one": 1, "two": 2}
for k, v := range s {
fmt.Println(k, v)
}
函数(方法),go语言中的函数与大多数语言中的函数(方法)都是差不多的,都是使代码得到充分的复用
格式 func name(name type,name type...)type{return;} 没有void,将返回类型省略就是等同于void
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
指针,go语言中的指针与c++中的类似,更加安全,主要是起着传参的作用,
格式 name *type,在函数中传入参数时,通过传入参数的地址给形参,再通过函数对形参的改变能够直接改变这个参数地址对应的值,即就是改变实参的值,若不通过指针的方式,则只是将值赋值给形参,函数改变的也就只是形参的值,对实参没有影响,
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)//传入的是地址,所以需要&符号
fmt.Println(n) // 7
}
结构体,go语言中的结构体与c++中的类似,都是可以作为一种数据类型去定义变量
格式 type name struct{},定义了结构体就可以将它作为一种数据类型去定义变量,赋初值的方式可以有多种,可以通过 '.'来访问结构体内的元素,并进行处理
也可以和指针连用,形成结构体指针,也会改变原结构体的值,原理和指针传参是一致的
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}//一一对应的赋值
b := user{"wang", "1024"}//直接赋值
c := user{name: "wang"}
c.password = "1024"//通过'.'来访问结构体内的元素
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(&a, "haha")) // false
fmt.Println(a.name) // zhang
}
func checkPassword(u *user, password string) bool {
u.name = "zhang"
return u.password == password
} //指针也同样使用,结构体指针
结构体方法,只为结构体服务的方法,只对结构体内部进行处理
格式 func (structname struct) methodname (name type)type{} ,形参必须是结构体中有的变量,要是改变原结构体的值就得和指针结合,即在结构体前加*号
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
总结:与以往学习的语言相比,go语言更加的高效,结合了多种语言的特点,自我感受其中c++和python的特点更为明显,既实现了类似于c++的高效率,又实现类似于python的简洁代码。
学习的建议:我刚开始学的时候也遇到了许多问题,但是都一一解决了,老师讲的很迅速,很多细节要我们自己去抠,看不懂的就反复看,或者去其他网站搜相关知识点,对于基础性知识不能马虎,得搞明白搞清楚,遇到的许多问题不要轻易就放弃,各个网站都搜一遍,有资源就多利用,对于不熟悉的知识,可以通过刷题来反复锤炼,一个问题可以用多种语言来解决,这样可以使得自己对这门语言更加了解,总而言之,学习永远都只是自己的事,主动学习方能学好学精,看不懂的就去查去学,没有什么是拿不下来的!