初始go
[TOC]
初始GO
基础语法
hello world
一个项目只有一个包
main
而且在
main
里面有一个func main
注释
1 |
|
简单的程序
小程序
1
2
3
4
5
6
7
8
9
10
11
12package main
import "fmt"
func main() {
var name string = "awd"
fmt.Println(name)
name = "Awdawd"
fmt.Println(name)
var Int int = 2
fmt.Println(Int, name)
}- 注意
Println
的不一样的用法
- 注意
变量
声明变量
定义单个变量
var 变量名 变量的类型
var 变量名
变量名 :=
定义多个变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package main
import "fmt"
func main() {
var (
name string = "ad"
Int int = 9
)
fmt.Println(name)
fmt.Println(Int, name)
print(Int)
}注意var()用法
变量的初始值
go
语言可以为变量默认值自动推导
使用
变量名 :=
不用加var
1
2
3
4
5
6
7
8
9package main
import "fmt"
func main() {
name := "AWda"
fmt.Println(name)
}打印类型
%T
大写打印地址
%p
小写_
给这个赋任何值都应该该被抛弃
变量交换
1
2
3
4
5
6
7
8
9
10
11package main
import "fmt"
func main() {
var a int = 100
var b int = 200
a, b = b, a
fmt.Println(a, b)
}变量的作用域
- 如果存在全局变量和局部变量,在函数里面优先使用局部变量
常量
使用关键字
const
也可以使用自动推导
1
2const d, e, c = "string", 3.14, 520
const a int =4;iota
常计数器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15func main() {
const (
a = iota
b
c
d = "haha"
e
f = 100
g
h = iota
i
)
fmt.Println(a, b, c, d, e, f, g, h, i)
}b=1,c=2
e=haha会与上面的变量保持相同
g=100同理
h=7恢复计数
i=8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21func main() {
const (
a = iota
b
c
d = "haha"
e
f = 100
g
h = iota
i
)
const (
j = iota
)
fmt.Println(a, b, c, d, e, f, g, h, i, j)
}
//j=0;
//是一组新的const
数据类型
bool
默认值为
false
打印格式是
%t
整形
有符号整型:
int8
、int16
、int32
、int64
和int
。无符号整型:
uint8
、uint16
、uint32
、uint64
和uint
。分别表示 8 位、16 位、32 位、64 位和字节长度的无符号整数。
浮点型
当规定输出的小数点时,go会四舍五入
1
2
3
4
5
6
7func main() {
var float float32 = 3.14
var float2 float32 = 3.19
fmt.Printf("%f\t%f\n", float, float2)
fmt.Printf("%.1f\t%.1f", float, float2)
}运行结果
1
2
33.140000 3.190000
3.1 3.2
部分类型的别名
string
使用%s打印
字符串拼接
1
2var str string = "AWDawd"
fmt.Println(str+"awd")
和C一样的转义字符
数据类型的转换
1
2
3
4
5
6
7
8
9a := 3
b := 3.14
fmt.Println((float64(a) + b))
//将a转换为float64
c:=float64(int(float64(a)))
println(c)- 整形,浮点型不能转成bool
切片
append()
向切片添加1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package main
import "fmt"
func main() {
var ints []int = make([]int, 5)
fmt.Printf("before\n")
for _, v := range ints {
fmt.Println(v)
}
fmt.Println("after")
ints = append(ints, 2)
ints = append(ints, 2312)
for _, v := range ints {
fmt.Println(v)
}
b := make([]int, 5)
copy(b, ints)
for _, v := range b {
fmt.Println(v)
}
}
map
创建
a:=make(map[key]value)
1
2
3
4
5
6a := make(map[string]int)
a["noe"] = 1
a["two"] = 2
for _, v := range a {
fmt.Println(v)
}读取
v, ifHava := a["three"]
- v代表了值
- ifHava 类型是bool看是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14func main() {
a := make(map[string]int)
a["noe"] = 1
a["two"] = 2
for _, v := range a {
fmt.Println(v)
}
v, ifHava := a["three"]
if ifHava == true {
fmt.Println(v)
} else {
fmt.Println("NO")
}
}删除
delete(a, "one")
a
是一个map
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16func main() {
a := [3]int{2, 3, 4}
sum := 0
for i := range a {
sum += i
}
fmt.Println(sum)
m := map[string]string{"a": "1", "b": "2"}
for k, v := range m {
fmt.Println(k, v)
}
}for range
前面的相当于下标,所以直接是key
结构体
起别名+创建结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 起别名
type chg int
// 定义一个结构体
type zjy struct {
name string
high float64
}
func changeZjy(test *zjy) {
test.high = 1.5
test.name = "zjy is big pig"
}
func main() {
var int1 chg = 2
fmt.Println(int1)
fmt.Printf("%T\n", int1)
var test zjy
test.high = 2.4
test.name = "zjy"
fmt.Println(test)
changeZjy(&test)
fmt.Println(test)
}
将结构体变换类
- 注意
- 记得在外挂函数的时候传指针
- 声明结构体,记得通过大小写来限定权限
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28type Person struct {
name string
age int
high float64
}
func (this *Person) setName(name string) {
this.name = name
}
func (this *Person) getName() string {
return this.name
}
func (this *Person) Show() {
fmt.Println(*this)
}
func main() {
var zjy Person
zjy.setName("zjy")
fmt.Println(zjy.getName())
fmt.Println(zjy.name)
zjy.Show()
zjy.age = 20
zjy.high = 1.5
zjy.Show()
fmt.Println(zjy)
}- 注意
算数运算符
- 基本的都和C语言一样,下面是不一样的
- 自增自减只有
i++
,i--
关系运算符
- 和C语言一样
逻辑运算符
- C语言一样
位运算符
&
两个二进制数对应都为1,才为1相当于和
|
只要有1,就为 1相当于或
打印二进制(b是二进制的缩写)
1
fmt.Printf("%b", b)
^
不同为1
,相同为0
<< n
左移n位>> n
右移n位
输入和输出
使用fmt
里面的scan
等
1 |
|
选择语句
if-else
注意不加小括号
1 |
|
switch
默认op为true
go
的op
可以是任何数据类型
switch
没有穿透性
1 |
|
fallthrough
可以提供穿透性
1 |
|
配合使用break
可以跳出穿透
1 |
|
加强的swith
1 |
|
死循环
1 |
|
加强的for
(遍历是数组和切片)
注意range
会返回两个值,第一个是下标,第二个是value
1 |
|
字符串
求长度使用
len
和
java
一样string
是不可以修改的字符串其他操作
遍历查询string
1
2
3
4
5
6
7
8
9
10
11package main
import "fmt"
func main() {
str := "awd"
fmt.Println(len(str))
for i := 0; i < len(str); i++ {
fmt.Printf("%c", str[i])
}
}数组
1
2
3
4
5
6func main() {
a := []int{1, 2, 3, 4}
for i, v := range a {
fmt.Println(i, v)
}
}数组可以直接打印
1
2
3
4
5
6
7func main() {
ints := []int{1, 2, 3, 4, 5}//这是一个切片,没有固定的大小,像vector
ints2 := [12]int{0}//这是一个数组
log.Println(ints)
}打印结果
[1 2 3 4 5]
字符串
字符串的创建和转换
str := "hello"
:使用双引号或反引号创建字符串。str := string([]byte{'h', 'e', 'l', 'l', 'o'})
:将字符切片转换为字符串。str := strconv.Itoa(123)
:将整数转换为字符串。
字符串的长度和索引
len(str)
:获取字符串的长度(单位为字节)。str[index]
:获取指定索引位置的字符。
字符串的拼接和分割
str := str1 + str2
:将两个字符串拼接起来。strings.Join(strs []string, sep string) string
:将多个字符串拼接成一个字符串,中间用sep
分隔。strings.Split(str string, sep string) []string
:将字符串按照sep
分割成多个子串,返回一个字符串切片。
字符串的查找、比较和替换
strings.Contains(str string, substr string) bool
:判断字符串str
是否包含子串substr
。strings.Index(str string, substr string) int
:返回子串substr
在字符串str
中第一次出现的位置,若不存在则返回-1
。strings.Replace(str string, old string, new string, n int) string
:将字符串中的old
替换为new
,如果指定了n
,则最多替换n
次。
字符串的转换和格式化
strconv.Atoi(str string) (int, error)
:将字符串转换为整数。strconv.ParseFloat(str string, bitSize int) (float64, error)
:将字符串转换为浮点数。fmt.Sprintf(format string, a ...interface{}) string
:类似于Printf
,但是返回一个字符串,而不是将结果输出到标准输出。
函数定义
一个加法函数
多注意参数列表的省略
1
2
3
4func add(a, b int) int {
return a + b
}一个传址的函数
1
2
3func add2(a, b *float64) float64 {
return *a + *b
}形式参数,和实际参数
形式参数:函数的参数列表
实际参数:调用函数时传的参数
可变参数列表
1
2
3
4
5
6
7
8
9
func add3(args ...int) int {
SUM := 0
for _, v := range args {
SUM += v
}
return SUM
}
可以使用len()计算可变参数长度
每一个函数只能写一个一个可变参数
any
1
var ANY any
参数的传递
值传递
基础数据类型,array,struct
注意使用传递数组的时候,我们需要将形参的数组的的的大小和实参数组的大小同一
1
2
3
4
5
6
7
8
9
10
11
12
13func test(ints [3]int) {
fmt.Println(ints)
ints[0] = 100
fmt.Println(ints)
}
func main() {
ints2 := [3]int{1, 2, 3} //这是一个数组
fmt.Println(ints2)
test(ints2)
fmt.Println(ints2)
}运行结果:
[1 2 3]
[1 2 3]
[100 2 3]
[1 2 3]可见没被更改
引用传递
切片
1
2
3
4
5
6
7
8
9
10
11
12func test(ints []int) {
fmt.Println(ints)
ints[0] = 100
fmt.Println(ints)
}
func main() {
ints2 := []int{1, 2, 3} //这是一个数组
fmt.Println(ints2)
test(ints2)
fmt.Println(ints2)
}运行结果
[1 2 3]
[1 2 3]
[100 2 3]
[100 2 3]直接传地址
1
2
3
4
5
6
7
8
9
10
11
12func test(ints *int) {
fmt.Println(*ints)
*ints = 100
fmt.Println(*ints)
}
func main() {
ints2 := 2
fmt.Println(ints2)
test(&ints2)
fmt.Println(ints2)
}运行结果
2
2
100
100
变量的作用域
- 近者优先使用
函数的递归
1 |
|
推迟(延迟)函数
defer
修饰调用函数表示在最后调用
如果有多条
defer
则defer语句会逆序调用,也就是类型栈,先声明的最后调用表面上是最后执行,其实是顺序编译,放进调用栈,在这个时候就已经传参。所以出现多个
defer
时出现先调用后执行
函数的数据类型
函数本身也是一个数据了类型,类似C语言的函数指针
func main()
1
2
3
4
5
6
7
8func main() {
test()
}
func test() {
fmt.Printf("%T", test) //打印函数的类型
}运行结果
func()
func test() int
1
2
3
4
5
6
7
8
9
func main() {
test()
}
func test() int {
fmt.Printf("%T", test) //打印函数的类型
return 1
}运行结果
func() int
func(int, int) (int, int)
1
2
3
4
5
6
7
8func main() {
test(1, 2)
}
func test(a int, b int) (int, int) {
fmt.Printf("%T", test) //打印函数的类型
return 1, 2
}运行结果
func(int, int) (int, int)
类比函数指针
无名函数
通过函数类型进行函数赋值
1
2
3
4
5
6
7
8
9
10
11
12func main() {
var test2 func(int, int) (int, int)
test2 = test
test2(1, 2)
}
func test(a int, b int) (int, int) {
fmt.Printf("%T", test) //打印函数的类型
return 1, 2
}可以看成C语言的函数,或者c++的可调用对象
无名函数
相当于c++的lambda表达式
不写函数名,直接写
()
1
2
3
4
5
6func main() {
func(a, b int) {
fmt.Println(a, b)
fmt.Printf("this is lambda")
}(1, 2)
}函数也可以当作函数的参数,就是回调函数
函数作为另一个函数的
return
,可以形成闭包结构
闭包
//没学懂
结构体函数
在func后面添加结构体
参考代码实例
1
2
3
4
5
6
7
8
9
10
11
12type Student struct {
name string
ids string
}
func (s Student) getName() string {
return s.name
}
func (s Student) getIds() string {
return s.ids
}
错误定义
import "errors"
errors.New(string)
返回错误的类型
获取时间
import "time"
数字的解析
f, _ := strconv.ParseFloat("1.421", 64)
继承
继承&重载
1 |
|
多态
通过接口实现多态,也就是相当
c++
的纯虚基类接口本质是一个指针
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51// 多态的实现
type Animal interface {
//只能有函数
GetType() string
GetColor() string
//kind string
//color string
}
type Cat struct {
kind string
color string
}
func (this *Cat) GetType() string {
return this.kind
}
func (this *Cat) GetColor() string {
return this.color
}
type Dog struct {
kind string
color string
}
func (this *Dog) GetType() string {
return this.kind
}
func (this *Dog) GetColor() string {
return this.color
}
func GetAnimal(animal Animal) {
fmt.Println("color is:", animal.GetColor())
fmt.Println("type is :", animal.GetType())
}
func main() {
cat := Cat{"Cat", "blue"}
GetAnimal(&cat)
dog := Dog{
kind: "Dog",
color: "yellow",
}
fmt.Println(dog)
GetAnimal(&dog)
}
万能类型
interface{}
相当于void
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23type Person struct {
name string
age int
}
func function(arg interface{}) {
fmt.Println(arg)
fmt.Printf("and type is: %T \n", arg)
}
func main() {
person := Person{
name: "chg",
age: 20,
}
var f float32 = 2.5
function(2.4)
function(f)
function(1)
function(person)
}
万能类型提供断言,可以查看是否是某种变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import "fmt"
func IsString(arg interface{}) {
value, isString := arg.(string)
if isString {
fmt.Println(value)
fmt.Println("this is string")
} else {
fmt.Println(value) //不是的话打印换行符
fmt.Println("this not string")
}
}
func main() {
IsString("string")
IsString(3.4)
}变量里面的
pair
每一个变量都有一个
pair
于是可以对
反射
reflect
包提供了两个函数用于获取变量的
pair
从而获取到值和类型这个用于对于未知类型变量的处理
结构体标签
语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import (
"fmt"
"reflect"
)
type Person struct {
Name string `info:"name" doc:"我的名字"`
High float64 `info:"high"`
Age int `info:"age"`
}
func findTag(T any) {
str := reflect.TypeOf(T)
for i := 0; i < str.NumField(); i++ {
tag := str.Field(i).Tag
fmt.Println(tag)
}
}
func main() {
var person Person
findTag(person)
}
线程和协程
创建
goroutine
相当于创建一个线程只用普通函数创建线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23import (
"fmt"
"time"
)
func goRoutine() {
for i := 0; i < 10; i++ {
fmt.Println("this is goroutine : ", i)
time.Sleep(time.Second)
}
}
func main() {
i := 0
go goRoutine()
for i < 10 {
fmt.Println("this is main : ", i)
time.Sleep(1 * time.Second)
i++
}
}
使用匿名函数创建线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func main() {
go func() {
defer fmt.Println("this is A.defer")
i := 0
for i < 10 {
i++
fmt.Println("this is goRoutine : ", i)
time.Sleep(1 * time.Second)
}
fmt.Println("A")
func() {
runtime.Goexit() //退出当前的goroutine
defer fmt.Println("this is B.defer")
fmt.Println("this is B")
}()
}() //加小括号是为了调用
线程之间的沟通
chan
使用chan 保持线程之间的联系,这个变量能够维持线程之间的秩序,保持main后退出。也就是说如果chan没有值,就会阻塞
1
2
3
4
5
6
7
8
9
10
11func main() {
defer fmt.Println("main.defer")
var c chan any = make(chan any)
go func() {
defer fmt.Println("A.defer")
c <- 888
}()
g := <-c
fmt.Println(g)
}无缓冲的chan
当一个
goroutine
向无缓冲的chan
发送数据时,如果没有其他goroutine
正在等待接收数据,发送的goroutine
会被阻塞,直到有其他goroutine
准备好接收数据为止。同样地,当一个goroutine
从无缓冲的chan
接收数据时,如果没有其他goroutine
正在等待发送数据,接收的goroutine
也会被阻塞,直到有其他goroutine
准备好发送数据为止。无缓冲的
chan
可以用于实现两个goroutine
之间的同步,确保在数据交换之前两个goroutine
都准备好。它们可以用于控制并发的执行顺序,防止数据竞争和资源争用。在例子中使用make(chan any)创建
1
2
3
4
5
6
7
8
9
10
11func main() {
defer fmt.Println("main.defer") // 创建了一个没有缓冲的channel
var c chan any = make(chan any)
go func() {
defer fmt.Println("A.defer")
c <- 888
}()
g := <-c
fmt.Println(g)
}有缓冲的chan
有缓冲的
chan
允许在发送(chan <- value
)和接收(value <- chan
)数据时不会立即发生阻塞,除非缓冲区已满或为空。当缓冲区未满时,发送操作会将数据放入缓冲区,并立即返回,而不会阻塞发送的goroutine
。同样地,当缓冲区不为空时,接收操作会从缓冲区中取出数据,并立即返回,而不会阻塞接收的goroutine
。当缓冲区已满时,发送操作会导致发送的
goroutine
阻塞,直到有其他goroutine
从缓冲区中取出数据为止。同样地,当缓冲区为空时,接收操作会导致接收的goroutine
阻塞,直到有其他goroutine
向缓冲区发送数据为止。例子中使用make(chan int,3)进行创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
defer fmt.Println("main 工作完毕")
c := make(chan int, 3) // 创建了一个有三个缓冲的channel
go func() {
defer fmt.Println("子线程工作完毕")
i := 0
for i < 4 {
i++
c <- i
fmt.Println("子线程 传输元素为:", i, " len(c) ", len(c), " cap(c) ", cap(c))
}
}()
time.Sleep(time.Second * 2)
for i := 0; i < 4; i++ {
num := <-c
fmt.Println("main 接收到元素为:", num, " len(c) ", len(c), " cap(c) ", cap(c))
}
time.Sleep(1 * time.Second)
}
关闭channel
channel和range的关系
也就是说我们可以使用
range
遍历channel
channel
和select
可以多路的监控
channel
状态