go笔记

 

一.参考资料

Go 语言之旅

https://golang.halfiisland.com/essential/base/0.ready.html

主要根据这两学的

感觉像C与pyhon的中间态,不过偏向python更多

二.包

1.package

每个Go程序都从包开始,同一个目录下的包名必须相同,如果要生成可执行文件,main函数所在的包必须得命名为main

如何引用自己本地的包?

假设你的文件结构是这样

text-->

    main.go

      hh-->

        a.go

        b.go

其中tools是你自己的工具包

你需要在text目录下创建mod,具体指令是go mod init text,你也可以变成go mod init 66.com/66/text反正只要你最后一个是你的根目录名字就行

如果要导入就得使用import

按这个结构第一种方法就是import"text/hh"

然后假如a,b的包名,也就是一开始声明的package为tools

直接在主函数里使用就行了tools.XXX()

注意工具包里的函数的首字母一定要大写,这是公有的体现,如果是小写就会报错

2.import

导入一个包就是导入这个包的所有公有的类型/变量/常量,导入的语法就是import加上包名

可以这样写

package main

import "example"
import "example1"

也可以这样写:

package main

import (
  "example"
  "example1"
)

起别名

package main

import (
  e "example"
  e1 "example1"
)

不过注意引用的包必须使用,不然会报错

但…………

我就简单写个hello,word

然后go build编译

注意到程序大小为什么那么大,用ida一看…………

??????

为什么什么包都编译上去了

不许我做

就许你做是吧,我本来还以为是减少储存占用才这样……

三.数值

1.声明:

声明一个变量的基本语法是 var,类型声明放在最后

var a,b,c,d int

也可以像这样

使用:=,go会根据后面的数据自动声明类型

a:=10//int
b:="a"//string
c:=True//bool

2.常量:

常量不能用 := 语法声明。

只能const

四,循环

只有for循环,for中条件不用加括号,其他和C都一样

1.for range

for range可以更加方便的遍历一些可迭代的数据结构,如数组,切片,字符串,映射表,通道。语句

用法如下:

index为可迭代数据结构的索引,value则是对应索引下的值,例如使用for range遍历一个字符串。

func main() {
   sequence := "hello world"
   for index, value := range sequence {
      fmt.Println(index, value)
   }
}

也可以当成python用

for range也可以迭代一个整型值,字面量,常量,变量都是有效的。

for i := range 10 {
    fmt.Println(i)
}

n := 10
for i := range n {
    fmt.Println(i)
}

const n = 10
for i := range n {
  fmt.Println(i)
}

五,条件判断

1.if

条件不用加括号,其他和C一样

不过多了可以在条件之前可以执行一个简短语句

2.switch

Go 只会运行选定的 case,而非之后所有的 case。 在效果上,Go 的做法相当于这些语言中为每个 case 后面自动添加了所需的 break 语句。在 Go 中,除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switchcase 无需为常量,且取值不限于整数。

并将case从上到下执行

package main

import (
	"fmt"
	"runtime"
)

func main() {
	fmt.Print("Go 运行的系统环境:")
	switch os := runtime.GOOS; os {
	case "darwin":
		fmt.Println("macOS.")
	case "linux":
		fmt.Println("Linux.")
	default:
		// freebsd, openbsd,
		// plan9, windows...
		fmt.Printf("%s.\n", os)
	}
}

六,输入输出

1.输出:

fmt.Println标准的

os.stdout用来输出文件,也可以用来输出字符串

2.输入

// 扫描从os.Stdin读入的文本,根据空格分隔,换行也被当作空格
func Scan(a ...any) (n int, err error)

// 与Scan类似,但是遇到换行停止扫描
func Scanln(a ...any) (n int, err error)

// 根据格式化的字符串扫描
func Scanf(format string, a ...any) (n int, err error)

读取两个数字

func main() {
  var a, b int
  fmt.Scanln(&a, &b)
  fmt.Printf("%d + %d = %d\n", a, b, a+b)
}

读取固定长度的数组

func main() {
  n := 10
  s := make([]int, n)
  for i := range n {
    fmt.Scan(&s[i])
  }
  fmt.Println(s)
}

七,数组与切片

数组甚至不能动态定义大小,连C都不如

切片感觉没有python灵活好用,限制好多

1,数组

数值声明只能是一个常量不能是一个变量

// 正确示例
var a [5]int
nums := [5]int{1, 2, 3}
nums := [...]int{1, 2, 3, 4, 5} //等价于nums := [5]int{1, 2, 3, 4, 5},省略号必须存在,否则生成的是切片,不是数组
nums := new([5]int)//获取指针
// 错误示例
l := 1
var b [l]int

可以通过len()来看数组数据大小

cap()来看数组容量

还有一些类python的性质,不过数组在切割后,就会变为切片类型

nums := [5]int{1, 2, 3, 4, 5}
nums[1:] // 子数组范围[1,5) -> [2 3 4 5]
nums[:5] // 子数组范围[0,5) -> [1 2 3 4 5]
nums[2:3] // 子数组范围[2,3) -> [3]
nums[1:3] // 子数组范围[1,3) -> [2 3]

2.切片

切片在 Go 中的应用范围要比数组广泛的多,它用于存放不知道长度的数据,且后续使用过程中可能会频繁的插入和删除元素。

类似于python中的列表

初始化

var nums []int // 值
  nums := []int{1, 2, 3} // 值
nums := make([]int, 0, 0) // 值
  nums := new([]int) // 指针

插入元素

切片元素的插入也是需要结合append函数来使用,现有切片如下,

nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

从头部插入元素

nums = append([]int{-1, 0}, nums...)
fmt.Println(nums) // [-1 0 1 2 3 4 5 6 7 8 9 10]

从中间下标 i 插入元素

nums = append(nums[:i+1], append([]int{999, 999}, nums[i+1:]...)...)
    fmt.Println(nums) // i=3,[1 2 3 4 999 999 5 6 7 8 9 10]

从尾部插入元素,就是append最原始的用法

nums = append(nums, 99, 100)
    fmt.Println(nums) // [1 2 3 4 5 6 7 8 9 10 99 100]

这个比不上python里的列表,如果想切片插切片只能这样做,b后加...

a :=[5]int{1,2,3,4,5}
b := a[2:]
b = append(b,6)
c:=a[:]
c=append(c,b...)

删除元素

切片元素的删除需要结合append函数来使用,现有如下切片

nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

从头部删除 n 个元素

nums = nums[n:]
fmt.Println(nums) //n=3 [4 5 6 7 8 9 10]

从尾部删除 n 个元素

nums = nums[:len(nums)-n]
fmt.Println(nums) //n=3 [1 2 3 4 5 6 7]

从中间指定下标 i 位置开始删除 n 个元素

nums = append(nums[:i], nums[i+n:]...)
    fmt.Println(nums)// i=2,n=3,[1 2 6 7 8 9 10]

删除所有元素

nums = nums[:0]
fmt.Println(nums) // []

拷贝

切片在拷贝时需要确保目标切片有足够的长度,例如

func main() {
    dest := make([]int, 0)
        src := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(src, dest)
        fmt.Println(copy(dest, src))
        fmt.Println(src, dest)
    }
[1 2 3 4 5 6 7 8 9] []
0
[1 2 3 4 5 6 7 8 9] []

将长度修改为 10,输出如下

[1 2 3 4 5 6 7 8 9] [0 0 0 0 0 0 0 0 0 0]
9
[1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9 0]

八,字符串

和python一样无法修改,但可以转换为字节数组,然后修改

 str := "this is a string"
   // 显式类型转换为字节切片
   bytes := []byte(str)
   fmt.Println(bytes)
   // 显式类型转换为字符串
   fmt.Println(string(bytes))

然后其他的东西基本和python相同。

九,映射表?字典!

在 Go 中,map的实现是基于哈希桶(也是一种哈希表),所以是无序的

但比python差一点,不能镶嵌字典

初始化

map[keyType]valueType{}

例子

 mp := map[int]string{
   0: "a",
   1: "a",
   2: "a",
   3: "a",
   4: "a",
}

mp := map[string]int{
   "a": 0,
   "b": 22,
   "c": 33,
}

或者make

map 是引用类型,零值或未初始化的 map 可以访问,但是无法存放元素,所以必须要为其分配内存。

mp := make(map[string]int, 8)

mp := make(map[string][]int, 10)

十,指针

灵活性比C差一点,其他和C一样

不能指针运算!

创建

关于指针有两个常用的操作符,一个是取地址符&,另一个是解引用符*。对一个变量进行取地址,会返回对应类型的指针

    var p *int
	a:=1
	p=&a
	fmt.Println(*p)
	fmt.Println(p)
 1
0xc00010c0f0 

或者

	p:=new(int)
	a:=1
	p=&a
	fmt.Println(*p)
	fmt.Println(p)
}

十一,函数

声明

func sum(a int, b int) int {
    return a + b
}
//或
var sum = func(a int, b int) int {
    return a + b
}

返回值可以像python一样有多个

匿名函数

匿名函数就是没有签名的函数,例如下面的函数func(a, b int) int,它没有名称,所以我们只能在它的函数体后紧跟括号来进行调用。

func main() {
   func(a, b int) int {
      return a + b
   }(1, 2)
}

也可以这样,在参数里创建一个函数

slices.SortFunc(people, func(p1 Person, p2 Person) int {
    if p1.Name > p2.Name {
      return 1
    } else if p1.Name < p2.Name {
      return -1
    }
    return 0
  }
               )

闭包

和python里的Lamda差不多

不过我没有怎么用过

例子

func main() {
  grow := Exp(2)
  for i := range 10 {
    fmt.Printf("2^%d=%d\n", i, grow())
  }
}

func Exp(n int) func() int {
  e := 1
  return func() int {
    temp := e
    e *= n
    return temp
  }
}

输出

2^0=1
2^1=2
2^2=4
2^3=8
2^4=16
2^5=32
2^6=64
2^7=128
2^8=256
2^9=512

延迟调用defer

例子

func main() {
  Do()
}

func Do() {
  defer func() {
    fmt.Println("1")
  }()
  fmt.Println("2")
}

输出

2
1

不过这个只能延迟返回值,其他的照旧,就像这样,猜猜有返回什么?

func main() {
	defer fmt.Println(a())
	fmt.Println("c")
	
}

func a()int{
	fmt.Println("a")
	fmt.Println("b")
	return 1
}

输出

a
b
c
1

十二,结构体

Go没有类,但可以用结构体达成类似的效果

声明

type Programmer struct {
  Name     string
  Age      int
  Job      string
  Language []string
}

//或者这样
type Rectangle struct {
  height, width, area int
  color               string
}

实例化

go不存在构造函数

只能像字典一样实例化

programmer := Programmer{
   Name:     "jack",
   Age:      19,
   Job:      "coder",
   Language: []string{"Go", "C++"},
}

或者像这样

type Person struct {
  Name    string
  Age     int
  Address string
  Salary  float64
}


func NewPerson(name string, age int, address string, salary float64) *Person {
  return &Person{Name: name, Age: age, Address: address, Salary: salary}
}
func main() {
 person := NewPerson("John", 30, "New York", 10000.0)
 fmt.Println(*person)
	
}

选项模式

type PersonOptions func(p *Person)

type Person struct {
  Name     string
  Age      int
  Address  string
  Salary   float64
  Birthday string
}

type PersonOptions func(p *Person)

func WithName(name string) PersonOptions {
  return func(p *Person) {
    p.Name = name
  }
}

func WithAge(age int) PersonOptions {
  return func(p *Person) {
    p.Age = age
  }
}

func WithAddress(address string) PersonOptions {
  return func(p *Person) {
    p.Address = address
  }
}

func WithSalary(salary float64) PersonOptions {
  return func(p *Person) {
    p.Salary = salary
  }
}

实际声明的构造函数签名如下,它接受一个可变长PersonOptions类型的参数。

func NewPerson(options ...PersonOptions) *Person {
    // 优先应用options
  p := &Person{}
    for _, option := range options {
        option(p)
    }

  // 默认值处理
  if p.Age < 0 {
    p.Age = 0
  }
  ......

    return p
}

这样一来对于不同实例化的需求只需要一个构造函数即可完成,只需要传入不同的 Options 函数即可

func main() {
  pl := NewPerson(
    WithName("John Doe"),
    WithAge(25),
    WithAddress("123 Main St"),
    WithSalary(10000.00),
  )

  p2 := NewPerson(
    WithName("Mike jane"),
    WithAge(30),
  )
}

十三,方法

方法与函数的区别在于,方法拥有接收者,而函数没有,且只有自定义类型能够拥有方法。先来看一个例子。

方法只有自定义类型才能使用

i可以换成其他的,和其他语言中的this,self差不多

package main

import ("fmt";
		)


type IntSlice []int

func (i IntSlice) Get(index int) int {
  return i[index]
}
func (i IntSlice) Set(index, val int) {
  i[index] = val
}

func (i IntSlice) Len() int {
  return len(i)
}


func main() {
	var intSlice IntSlice
	intSlice = []int{1, 2, 3, 4, 5}
	fmt.Println(intSlice.Get(2))
	intSlice.Set(0, 2)
	fmt.Println(intSlice)
	fmt.Println(intSlice.Len())
}

十四,泛型

泛型使得一个函数可以处理不同类型的数据

例如

我想实现两个数间的加法,对于不同类型得用不同的函数,太过于麻烦了

func Sum(a, b int) int {
   return a + b
}

func SumFloat64(a, b float64) float64 {
  return a + b
}

可以像这样实现

func Sum[T int | float64](a, b T) T {
   return a + b
}

十五,接口

接口是方法的集合

就我理解的和面向对象的区别:

面向对象是现有一个对象,比如说人

可以,可以

就是人的方法

而Go这种不是

是面向方法的

现有

然后想什么可以做这些事

可以,也能

动物也能

这就是我理解的不同

声明

使用 interface关键字

type Person interface {
  Say(string)
}

方法的实现与应用


type XiaoMing struct{
	world string
}
func (i XiaoMing)Say(s string){
	i.world = s
	fmt.Println(i.world)
}

type XiaoHong struct{
	world string
}
func (i XiaoHong)Say(s string){
	i.world = s
	fmt.Println(i.world)
}
func main() {
people:=People(XiaoMing{})
people.Say("hello,XiaoHong")
people=People(XiaoHong{})
people.Say("hello,XiaoMing")
}

后记

暂时就这么多,以后遇到再添加,能看懂代码就行

posted @ 2025-04-17 20:16  漫宿骄盛  阅读(40)  评论(0)    收藏  举报