- 一. 初识Golang
- 1.1 Go的语法要求
- 1.1.1 token
- 1.2 变量和常量
- 1.2.1 变量
- 1.2.2 常量
- 1.3 基本数据类型
- 1.3.1 布尔类型
- 1.3.2 整型
- 1.3.3 浮点型
- 1.3.4 复数类型
- 1.3.5 字符串
- 1.3.6 rune 类型
- 1.4 复合数据类型
- 1.4.1 指针
- 1.4.2 数组
- 1.4.3 切片
- 1.4.4 map
- 1.4.5 struct
- 1.5 控制语句
- 1.5.1 if 语句
- 1.5.2 switch 语句
- 1.5.3 for语句
- 二. 函数
- 2.1 函数的基本概念
- 2.2 init 函数
- 2.2.1 基本介绍
首先让我们问候一下世界
package main
import "fmt"
func main(){
fmt.Println("hello, world!n")
}
运行结果:
对世界进行了一波问候后,我们对这个简单的程序进行解读分析
(1)第一行我们定义了一个名为 package 的包,main是可执行程序的包名,所有的Go源程序文件头部必须有一个包声明语句。
(2)然后用 关键字 import 导入了一个“fmt”文件,fmt 是 format 的缩写,是一个标准的包,有点类似C语言中的头文件。
(3)关键字 func 声明定义了一个函数, 函数名为main, 在Go语言中 main 代表一个程序的入口,没有 main 函数的程序就像与一间没有没有门的密室,在C语言中也是如此。
(4)main函数里面调用了 fmt 包里的 Println 函数, 其中 “Hello, world!” 是一个字符串常量,而 n 是一个转义字符, 换行。
token 是构成源程序的基本不可再分割的单元,编译器的编译源程序的第一步就是将源程序分割为一个个独立的 token,这就是语法分析的过程,而token 又可以分为关键字,标识符,操作符、分隔符和字面常量等。
如图所示:
其中操作符本身就是一个天然的分隔符,同时其自身也是一个 token。
纯分隔符本身布局有任何语法意义,只是作为其他 token 的分割功能。例如空格、字符表、换行符等。
从1.2中的代码可以分析出
关键字:
package import func
标识符:
main fmt Println
字面量:
“fmt” “hello, world!n”
操作符:
( ) { } .
一个Golang源程序的基本就是由各种 token 构成。
基本框架:
package main
import (
"...."
)
func main(){
....
}
1.2 变量和常量
1.2.1 变量
变量的理解很简单,如其名,可以变化的量,其官方定义是指没有固定的值,可以改变的数。 通常在写程序时都需要用到各中数据,而变量能够方便程序员对内存数据进行访问。
1.变量的完整声明
var varName dataType = value var a int = 10 var a int = 3 * 5 var a int = b
1.短类型声明
varName := value //注意: //:= 声明只能出现在函数内 //Go编译器会自动进行数据类型推断
变量名的命名规则
开头字符必须是字母或下划线,后面可跟多个字符,数字或下划线。
常量”的广义概念是:‘不变化的量’(例如:在计算机程序运行时,不会被程序修改的量。在Golang中常量使用一个名称来绑定一块内存地址,且该内存地址里面存放的内容不可改变。
//类似枚举
package main
import "fmt"
const(
c0 = 2 << iota
c1
c2
c3
c4
)
func main(){
fmt.Println(c0, c1, c2, c3, c4)//结果:2 4 8 16 32
}
还有字符串常量,例如:
a := "hello world"1.3 基本数据类型
Golang 内置有七类基本数据类型
1.3.1 布尔类型布尔类型:bool
整形:byte int int8 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
浮点型:float32 float64
复数:complex64 complex128
字符:rune
字符串:string
布尔类型只有两个值,true 和 false,true 代表真,false 代表假,是Go内置的两个预声明标识符。
var a bool
a = true
//or
a := false
//布尔类型数据和整数不能进行相互转换
var a bool
a = 1 //error 1是整型字面量
//逻辑比较判断表达式的返回值都是布尔类型数据
//条件成立的返回值为true,否则返回false
x := 2
y := 1
var b bool = x > y //b = true
var b bool = (x < 0) && (y > 0) // b = false
//if 和 for 语句中的条件部分的返回值也是bool类型
if a < b{
print(b)
}else{
printf(a)
}
for ; true; {//相当于C中的while(1)
}
//声明的bool类型变量如果没有进行初始化,则默认值为false
var c bool // c is false
1.3.2 整型
整型变量的定义方式:
//标准型 var a int a = 1 //缩减型 a := 1
Go中内置了12种整型类型,byte int int6 int8 int32 int64 uint uint8 uint16 uint32 uint64 uintptr,每种类型可容纳的数据大小不同,并且不同整型相互赋值需要强制转换
var a int = 1 var b int32 = 2 b = a //error b = (int32)a //true1.3.3 浮点型
浮点型变量用于表示存储包含小数的数据空间,Go中有 float32 和 float64 两种。
注意事项:
(1)浮点数字面量被默认为 float64 类型
var f = 1.00
(2)在学习C语言时,用浮点型数据进行计算经常出现小数点后面的数据有一些细微的差别,这是与精度丢失有关,计算机很难进行浮点数的精确表示和存储,因此两个浮点数之间不能进行 == 或 != 判断操作。
1.3.4 复数类型Golang 内置的复数形式有两种,分别是 complex64 和 complex128,复数在计算机中使用两个浮点数表示,一个表示实部,一个表示虚部。complex64 是两个 float32 构成的,complex128 是由两个float64 构成的。表示方法和数学表示法一样。
var value1 complex64 = 3.1 + 5.1 value2 := 3.1 + 6i //Go 有三个内置函数处理复数 var v = complex(2.1, 3) //构造一个复数 a := real(v) //返回复数实部 b := image(v) //返回复数虚部1.3.5 字符串
基本介绍:
字符串就是一串固定长度的字符连接起来的字符序列, Go 的字符串是由单个字节连接起来的,Golang 的字符串字节使用 UTF-8 编码标识 Unicode 文本。
案例演示:
package main
import "fmt"
func main(){
//演示 Golang 中 string 的基本使用
var address string = "上海黄浦区 1203"
fmt.Println(address)
}
运行结果:
字符串类型:string
注意事项和使用细节:
1.Golang 的字符串字节使用 UTF-8 编码标识 Unicode 文本, 这样 golang 统一使用 UTF-8 编码,中文乱码问题就不会困扰程序员了。
2.字符串一旦赋值就不可更改了,在 Go 中字符串是不可变的。
//字符串一旦赋值就不可更改了,在 Go 中字符串是不可变的 var str = "hello" str[0] = 'a'
运行:
3.字符串的表示形式
(1)双引号,会识别转义字符
(2)反引号,以字符串的原生形态输出,包括换行和特殊字符,可以实现防止输出、攻击源代码等效果。
str2 := "abcnadd" fmt.Println(str2)
结果:
反引 ``
str := `
package main
import "fmt"
func main(){
//演示 Golang 中 string 的基本使用
// var address string = "上海黄浦区 1203"
// fmt.Println(address)
//字符串一旦赋值就不可更改了,在 Go 中字符串是不可变的
// var str = "hello"
// str[0] = 'a'
// str2 := "abcnadd"
// fmt.Println(str2)
}`
fmt.Println(str)
运行:
字符串的拼接方式
str := "hello" + "world" str += "hehe"
运行:
4.基本数据转换string
方式一:使用 fmt包的方法
案例说明:
package main
import (
"fmt"
)
func main(){
//使用第一中方式来转换 fmt.Sprintf方法
var num1 int = 99
var str string //void str
str = fmt.Sprintf("%d", num1)
fmt.Printf("str type is %T, str = %qn", str, str)
//输出:str type is string, str = "99"
var num2 float64 = 120.345
str = fmt.Sprintf("%f", num2)
fmt.Printf("str type is %T, str = %qn", str, str)
//输出:str type is string, str = "120.345000"
var b bool = true
str = fmt.Sprintf("%t", b)
fmt.Printf("str type is %T, str = %qn", str, str)
//输出:str type is string, str = "true"
}
方式二:使用 strconv 包的方法
案例说明
//第二种方式:使用 strconv 函数
//func FormatInt(i int64, base int) string
//返回i的base进制的字符串表示。base 必须在2到36之间,
//结果中会使用小写字母'a'到'z'表示大于10的数字。
var num3 int = 16
var str string
str = strconv.FormatInt(int64(num3), 10)
fmt.Printf("str type is %T, str = %qn", str, str)
//输出:str type is string, str = "16"
1.3.6 rune 类型
GO 内置的两种字符类型:一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。另一种 是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。 rune 类型等价于 int32 类型。1.4 复合数据类型 1.4.1 指针
对于指针的理解,我个人认为并没有太大的困难,在学习C的时候,自从我学了指针和动态内存分配后,就再也没用过数组,并且还觉得用数组很low(如有冒犯,请保住 -_- 狗头),我个人认为指针是真的好用,非常的naice,指针的理解也很简单,指针和其他类型一样,也是一种变量,他是它是用来存储地址的变量,不同类型的地址对应不同类型的指针
图解:
Golang 中指针的特点:
(1)结构体指针访问结构体字段使用 “ . ” 点操作符, 没有“->”操作符
例如:
package main
import "fmt"
type persion struct{
name string
age int
}
func main(){
//方式1
//方式2
p2 := persion{"张山", 20}
fmt.Println(p2)
//方式3-&
var p3 *persion = new(persion)
//因为p3是指针,因此标准的给给字段赋值方式
(*p3).name = "李四" //也可以写成p3.name = "李四"
(*p3).age = 8439 //原因:go的设计者为了程序员方便,底层会对 p3.name = "李四" 进行处理
fmt.Println(*p3) //会给 p3 加上取值运算符 (*p3).name = "李四"
//方式4-{}
var p4 *persion = &persion{}//也可以 var p4 *persion = &persion{"suyue", 23}
//因为 p4 是一个指针,因此标准的访问字段的方法
//(*p4).name = ...
//也可以和上面一样使用
(*p4).name = "suyue"
(*p4).age = 23
fmt.Println(*p4)
}
(3)Go不支持指针运算(这点令我有点难受,可能C用的太习惯了)
(4)函数中允许返回局部变量的地址。
Go编译器使用“栈逃逸”机制将这种局部变量的空间分配在堆上。例如:
func add(a int, b int) *int{
sum := a + b
return &sun
}
1.4.2 数组
1.数组的定义
var name [n]elemetType
var arr [100]int //声明了一个有100个整型数据的数组
array := []float64{1.10, 4.54, 5.45, 9.21}
2.数组的初始化
arr := [4]int{1, 2, 3, 4}//即指定了长度有初始化了字面量
brr := []int{1, 2, 4, 5}//不指定长度,通过初始化的数量来定义数组的长度
crr := [3]int{1 : 1, 2 : 4}//指定长度,并通过索引值进行初始化
drr := []int{1 : 1, 2 : 3} //不知道总长度,通过索引值进行初始化
1.4.3 切片
切片的基本介绍
(1)切片的英文名时slice。
(2)切片是对数据的引用,因此切片是一个引用类型,在进行传递时,它遵守引用传递的机制。
(3)切片的长度是可变的,因此切片可以理解为一个长度可变的数组(有点类似于C中的动态内存分配)
(4)切片的标准定义
var sliceName []T var a []int
切片的基本使用
package main
import (
"fmt"
)
func main(){
//方法一:使用数组构造
var intArr = [...]int{1, 5, 4, 8}
slice := intArr[1 : 4] //对intArr从第二个元素
//开始到第三个元素结束
fmt.Println(slice) //[5 4 8]
//使用内置函数make创建切片
a := make([]int, 5, 10) //make(type, len, cap)
fmt.Println("a 的元素: ",a) //[0, 0, 0, 0, 0]
fmt.Printf("a 的长度len = %dn", len(a))//len = 5
fmt.Printf("a 的容量cap = %dn", cap(a))// cap = 10
//注意:直接声明切片变量时没有意义的
//例如
var b []int
fmt.Printf("%vn", b) //结果为 []
}
切片的内存分布
我们还能可以以结构体的形式理解:
type Slice struct{
array *int
len int
cap int
}
切片支持的操作:
(1)内置函数len()返回切片的长度(注意这里的长度只计算已初始化的部分)
(2)内置函数cap()返回切片的容量(容量指的是该切片所能存放数据的最大长度)
(3)内置函数append()对切片进行元素追加
(4)内置函数copy()用于切片的拷贝
a := [...] int {2, 4, 2, 6, 5, 9, 10}
b := make([]int, 5, 10)
c := a[2 : 4]
fmt.Printf("a的元素%v, 长度len=%d,容量cap=%dn", a, len(a), cap(a))
fmt.Printf("b的元素%vn", b)
b = append(b, 2)
b = append(b, c...)
fmt.Printf("b追加后,元素有%vn", b)//[0 0 0 0 0 2 2 6]
d := make([]int, 2, 10)
fmt.Println(d)//[0 0]
copy(d, c)
fmt.Println(d)//[2 6]
1.4.4 map
(1)map的基本概念
map 是 key-value 数据结构,由称字段或者关联数组。类似其他编程语言的集合,在编程中经常用到
对于 map 的理解,我认为可以将其与数组相比较,map的类型格式是:map[K]T,其中K指的是key的数据类型,T指的是valueType,数组是通过下标来对每个数据元素进行访问的,而下标明显都是一个整型的数据类型,而每个下标都对应着数组的一块类型空间,也可以看成是一个 intKey->T 的键值对,而数组下表的使用则是有语言的设计者设计好的,[ ] 中只能用整型的数据,当然有的语言也可通过字母对应的ASCLL吗值对数组进行访问,如C,而 map 却可以让我们自己设定一个数组的下标类型,例如:
package main
import "fmt"
func main(){
ma := map[string]int{"adv" : 1, "bffv" : 2}
fmt.Println(ma["adv"]) //"adv"->1
fmt.Println(ma["bffv"])//"bffv"->2
}
其中,要注意 slice map 和 func 类型不能作为 key 的类型
(2)map的创建
package main
import "fmt"
func main(){
//(1)map的创建
//使用字面量创建
ma := map[string]int{"a" : 1, "b" : 2}
fmt.Println(ma["a"])
fmt.Println(ma["b"])
//使用内置的make函数创建
mp1 := make(map[int]string)
mp2 := make(map[int]string, 10)
mp1[1] = "tom"
mp2[1] = "pony"
fmt.Println(mp1[1])
fmt.Println(mp2[1])
}
运行:
map 在某一方面还有了一点指针的味道 例如map[K]map[K]T,对于这种使用,我们那段代码来体验一下:
package main
import "fmt"
func modifUser(user map[string]map[string]string, name string){
//判断是否由此用户名
if user[name] != nil{
//如果有,则初始化passworld
user[name]["password"] = "you are T"
}else{
user[name] = make(map[string]string, 3)
user[name]["passworld"] = "232332323"
user[name]["nickname"] = "昵称:" + name
}
}
func main(){
var user map[string]map[string]string
user = make(map[string]map[string]string, 10)
//注意要在对 user["sms"] 进行make, 因为 "sms" 对应的值还是 map 类型
user["sms"] = make(map[string]string, 3)
user["sms"]["pawssworld"] = "dsafad"
user["sms"]["nickname"] = "sms"
fmt.Println(user)
modifUser(user, "sms")
modifUser(user, "yrs")
fmt.Println(user)
}
运行:
(3)map 支持的操作
1.map 的单个键值访问格式为 mapName[keyName],更新某个 key 的值时,mapName[keyName] 放到等号左边,访问某个值时放到等号右边。
2.可以使用 range 遍历一个 map 类型,但不能保证每次迭代元素的顺序,例:
package main
import "fmt"
func main(){
mp := make(map[int]string)
mp[1] = "xzy"
mp[2] = "yrs"
mp[3] = "sy"
mp[4] = "syj"
for _, v := range mp{
fmt.Println(v)
}
}
运行:
同样的数据每次输出的结果顺序却不一样。
3.删除 map 中的某个键值,可使用 delet ,语法结构是:delete(mapName, keyName)。delet 是内置函数。
delete(mp, 2) fmt.Println(len(mp)) //len = 31.4.5 struct
首先我们来说一下声明是结构体,结构体其实就是将一个事物的一些或所有信息提取出来,形成的一个新的变量,我们称为结构提变量,所以结构体也可以说是一种自定义的变量。
我们以一个学生为例,学生身上的信息有很多,例如:姓名、年龄、学号、性别、所在院系、身高、体重等等,我们可以将这些信息组合在一起,构建一个名为 student 的结构体变量,那我们就选取姓名、年龄、学号、性别这几个信息构建一个 student 的结构体变量,代码如下:
//构建一个 student 的结构体变量
type student struct{
name string //姓名
age int //年龄
Num int //学号
sex string //性别
}
然后我们便可以用 student 来定义变量,并对其进行信息的填入
var Stu student Stu.name = "yrs" Stu.age = 20 Stu.Num = 19 Stu.sex = "male"
完整代码如下:
package main
import "fmt"
//构建一个 student 的结构体变量
type student struct{
name string //姓名
age int //年龄
Num int //学号
sex string //性别
}
func main(){
var Stu student
Stu.name = "yrs"
Stu.age = 20
Stu.Num = 19
Stu.sex = "male"
fmt.Printf("Name:%sn", Stu.name)
fmt.Printf("Age:%dn", Stu.age)
fmt.Printf("Number:%dn", Stu.Num)
fmt.Printf("Sex:%sn", Stu.sex)
}
运行结果:
结构体在内存中的布局
当我们用 student 定义好一个 stu 的变量后,其在内存中的布局如下图所示:
结构体的基本介绍
(1)从概念上看,结构体字段 = 属性 = field。
(2)字段是结构体的一个重要组成部分,一般是基础数据类型、数组,也可以是引用类型。上面我们定义的 student 结构体的 name string 就是属性。
注意事项
1.在创建一个结构体变量后,如果没有给字段赋值,则它们都对应这一个默认值,其中,布尔类型是 false,字符串是"",指针,slice和map的默认值都是 nil,也就是说还没分配空间。
2.不同的结构体变量的字段是独立的,互不影响的。
接下来我们一代码的形式简介一下map在结构体中的使用:
package main
import "fmt"
type Persion struct{
name string
age int
scores [5]float64
slice []int
map1 map[string]string
}
func main(){
var p1 Persion
fmt.Printf("初始化前p1->%vn", p1)
p1.name = "yrs"
p1.age = 18
p1.scores[0] = 84.51
p1.scores[1] = 93.54
p1.scores[2] = 85.51
p1.scores[3] = 76.12
p1.scores[4] = 82.47
//使用slice一定要用make
p1.slice = make([]int, 10)
p1.slice[0] = 15
//fmt.Println(p1.slice)
//使用map要先make
p1.map1 = make(map[string]string, 10)
p1.map1["yrs"] = "rgsi"
//fmt.Println(p1.map1)
fmt.Printf("初始化后p1->%vn", p1)
}
创建结构体变量的方式
方法式一:直接声明
var p1 persion
方式二:
p2 := persion{"张山", 20}
方式三:指针形式
var p3 *persion = new(persion)
方式4:
var p4 *persion = &persion{}
完整案例:
package main
import "fmt"
type persion struct{
name string
age int
}
func main(){
//方式1
var p1 persion
//方式2
p2 := persion{"张山", 20}
fmt.Println(p2)
//方式3-&
var p3 *persion = new(persion)
//因为p3是指针,因此标准的给给字段赋值方式
(*p3).name = "李四" //也可以写成p3.name = "李四"
(*p3).age = 8439 //原因:go的设计者为了程序员方便,底层会对 p3.name = "李四" 进行处理
fmt.Println(*p3) //会给 p3 加上取值运算符 (*p3).name = "李四"
//方式4-{}
var p4 *persion = &persion{}//也可以 var p4 *persion = &persion{"suyue", 23}
//因为 p4 是一个指针,因此标准的访问字段的方法
//(*p4).name = ...
//也可以和上面一样使用
(*p4).name = "suyue"
(*p4).age = 23
fmt.Println(*p4)
}
1.5 控制语句
1.5.1 if 语句
语法规则
if {
//执行程序
}
//若与else连用
if {
//执行程序
}
else{
//执行程序
}
特点:
- Golang 的特点就是从简,因此判断条件不需要用括号括起。
- ”{“必须放在与 if 同一行上。
- if 后面可以带一个简单的初始化语句,并用分号隔开。
例如:
if a := 1;x < y {
//....
}
- Golang 没有条件运算符(a > b ? a : b)。
完整示例:
package main
import (
"fmt"
)
func main(){
var a int = 10
var b int = 32
if a > b {
fmt.Printf("a is the largest number!n")
}else if a == b {
fmt.Printf("a is equal to the bn")
}else{
fmt.Printf("b is the largest number!n")
}
输出结果:
switch 语句的作用其实和 if 、else if 的功能一样,都是在多种条件下做出分支,但当所需的分支很多是 switch 的写法会比if else 更简便。
举例:
package main
import (
"fmt"
)
func main(){
var a int = 1
switch a {
case 1:fmt.Printf("a is equal to the %dn", 1)
case 2:fmt.Printf("a is equal to the %dn", 2)
case 3:fmt.Printf("a is equal to the %dn", 3)
case 4:fmt.Printf("a is equal to the %dn", 4)
}
}
特点:
- switch 和 if 一样,后面的简单的初始化语句
- switch 后面的表达式是可选的,如果没有表达式,则 case 子句是一个 bool 表达式,而不是一个值。
- switch 的条件表达式不像C那样限制必须为整数,它可以是任意支持相等比较运算的类型变量。
- 通过 fallthough 语句来强制下一个case子句。
- switch 不用像C一样,要与break连用,执行完某个case子句后,不会在执行下面的case子句。
我们以对应C来介绍Golang里的for语句:
//1,类似C里的for循环语句
for init(初始值); condition(条件); post(变化){
}
//2.类似C里的while()循环语句
for condition{
}
//3.类似C里的while(1)死循环
for {
}
//4.for 还有另一种用法,是专门对数组、切片、字符串、map和通道的遍历
//语法如下:
//访问数组
for index, value := range arry{
}
//访问切片
for index, value := range slice{
}
//访问字符串
for index, value := range string{
}
//访问map
for index, value := range map{
}
//访问通道
for index, value := range channel{
}
二. 函数
首先我们先导入一下函数的基本概念。
2.1 函数的基本概念函数的概念:为完成某一功能的程序指令(语句)的集合,称为函数。
函数是Go程序源代码的基本构造单位,函数的定义包括:函数声明关键字func、函数名、参数列表、返回值列表和函数体。函数名的命名规则与标识符的基本一样,但其首字母的大小写决定了该函数在其它包的可见性,也就是说该函数能否在其他包中调用,其规则是:开头字母大写是则是公开的包,其他包可以调用,小写则为私密包,其他路径下的包不可调用。
函数命名的格式:
func funcName (param-list)(result-list){
function body
}
写法一:空类型,且不返回值
func f() {
fmt.Println("Hello world!")
}
写法二:单值返回
func f2()int{
return 1
}
写法三:多值返回
func f3()(int, int){
return 1, 2
}
写法四:省略返回
func f4(a, b int)(num int){
num = a - b
return
}
2.2 init 函数 2.2.1 基本介绍总结函数特点:
(1) 函数可以没有参数传递,也可以没有返回值。
(2) 多个相邻的相同类型的参数可以用逗号来简写。
(3) 在返回值列表中,支持有名的返回值,而该参数名可以作为函数内部的局部变量使用,并且返回时可以省略。
(4) 不支持默认值参数、函数重载、命名函数嵌套,但支持嵌套匿名函数
//init函数,通常用来完成初始化工作
func init(){
fmt.Printf("init.....n")
}
func main(){
fmt.Printf("main.....n")
}
每一个源文件都可以包含一个init函数,该函数会在main函数之前被Go运行框架调用,也就是说init函数会在main函数之前被使用。
案例说明:
运行结果:



