Go基础【2】

6️⃣ 标准输入

  • Scan
  • Scanf
  • Scanln
  • bufio,推荐使用
  • flag,命令行方式适合版本管理
	//Scan
    var (
		appName string
		version int
	)
	fmt.Println("请输入name:")
	fmt.Scan(&appName)
	fmt.Println("请输入version")
	fmt.Scan(&version)
	fmt.Printf("fmt.Scan appName:%s version:%d \n", appName, version)
	
	//Scanf
	_, err := fmt.Scanf("name=%s ver=%s", &appName, &version)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("fmt.Scanf appName:%s version:%s \n", appName, version)
	
	//Scanln
	fmt.Println("请输入name")
	fmt.Scanln(&appName)
	fmt.Println("请输入version")
	fmt.Scanln(&version)
	fmt.Printf("fmt.Scan appName:%s version:%s \n", appName, version)
	
	// bufio
	reader := bufio.NewReader(os.Stdin) // 从标准输入生成读对象
	fmt.Println("请输入:")
	text, _ := reader.ReadString('\n') // 读到换行
	//text, _ := reader.ReadString(' ') // 读到换行
	fmt.Println(text)

	//flag
	//go run -ldflags "-X 'main.appName=test' -X 'main.version=1'" main_test.go
	//go build -ldflags "-X 'main.appName=test' -X 'main.version=1'" main_test.go
	fmt.Printf("appName:%s version:%s \n", appName, version)

7️⃣ new & make

  • make: 分配内容 + 初始化 + 返回原始类型(T)
    • 只用于slice,map,channel
  • new: 分配内存 + 返回指针类型(*T)
    • 无限制
    • 填充0(仅初始化数组时)
// 源码定义
func make(t Type, size... IntegetType) Type // 例如初始化切片
func new(Type) *Type

请添加图片描述
以下初始化哪些为

  • s1 := make([]int, 0)
  • s2 := new([]int)
  • s3 := *new([]int)
  • var s4 []int
  • var s5 = []int{} //字面量初始化
package main

import "fmt"

func main() {

	// make 初始化切片
	var slice1 = make([]int, 10) // = make([]int,10,10) len = cap
	// [0 0 0 0 0 0 0 0 0 0] 10 10
	fmt.Println(slice1, len(slice1), cap(slice1))

	// new 初始化切片
	var slice2 = new([]int)
	// &[] 0 0
	fmt.Println(slice2, len(*slice2), cap(*slice2))

	s1 := make([]int, 0)
	// s1 0 0![请添加图片描述](https://img-blog.csdnimg.cn/direct/c516e73c51e54fb38537cbf8ef558d4d.png)

	fmt.Println("s1", len(s1), cap(s1))
	s2 := new([]int)
	s3 := *new([]int)
	var s4 []int
	var s5 = []int{} //字面量初始化

	fmt.Println("s1 is nil?", s1 == nil)  //false
	fmt.Println("s2 is nil?", *s2 == nil) //true
	fmt.Println("s3 is nil?", s3 == nil)  //true
	fmt.Println("s4 is nil?", s4 == nil)  //true
	fmt.Println("s5 is nil?", s5 == nil)  //false

	// 创建数组
	var arr1 = *new([10]int)
	var arr2 = [10]int{} //字面量初始化
	// [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
	fmt.Println(arr1, arr2)

}

8️⃣ 数组 切片 Map

数组切片Map
元素类型全部元素类型相同全部元素类型相同key值任何可比较值
地址连续连续的内存块连续的内存块不一定连续的内存块
访问元素时间O(1)O(1)O(1)【会更慢】
内存占用上连续,死连续,死灵活
零值元素类型零值nil
直接使用声明的指针类型否(nil)否(nil)否(nil)

关于元素类型的零值

  • 数组和结构体:对应元素类型
  • 整型:0
  • 字符串:“”
  • 其他:nil

① 初始化

数组

// var 字面量
arr1 := [10]int{} // 初始化会分配内存,不为nil
var arr2 [10]int  // 初始化会分配内存,不为nil
fmt.Println(arr1,arr2) // [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]
// new 指针
arr1 := new([10]int) // new会开辟空间
// var arr2 *[10]int = &[10] //错误写法,必须要初始化字面量
var arr3 *[10]int = &[10]{}
fmt.Println(arr1,arr3) // &[0 0 0 0 0 0 0 0 0 0] &[0 0 0 0 0 0 0 0 0 0]
// var 空指针情况
var arr1 *[10]int // 不会初始化分配内存
var arr2 [10]int  // 初始化会分配内存,不为nil
arr[1] = 1 // 空指针异常
fmt.Println(arr1) // nil
// 字面量
a1 := [4]string{1:"a","b","c","d"} //❌ 1:a b:2 c:3 d:4 d越界了,只能到下标3
a2 := [4]string{1:"a",0:"b","c"}   //❌ 1:a b:0 c:1 c重复下标了
a3 := [...]string{"a","b","c"} 
a3[3] = "f" 					   //❌

切片

var s1 = []int{}  		// 已初始化,不为nil
var s3 = make([]int,1)	// 已初始化,不为nil
var s2 = new([]int)		// 已初始化,不为nil

s1 = append(s1,1)
*s2 = append(*s2,1) 	// 指针类型,要解引用
var s1 []int 	  // 未初始化,为nil
var m map[int]int // 未初始化,为nil
s1 = append(s1,1) // ✔  append会自动开辟新的内存,操作运允许
m[1] = 1		  // ❌ 未初始化,操作不允许 
var arr1 *[]int 		// 未初始化,为nil
*arr1 = append(*arr1,1) // ❌ 未初始化,操作不允许

请添加图片描述

// 字面量
s1 := []string{2:"c",0:"a","b"} // 和数组一样 
fmt.Println(s1) // [a b c]

// 数组地址转切片
var s2 = new([10]int)[:]// =  var s2 = new([&10]int{})[:]

// 常量可以定义,变量不行
const x uint = 1
var y uint = 1 
s3 := []string{x:"b"} // ✔
s4 := []string{y:"b"} // ❌

小练习

package main

import "fmt"

func main() {
	var s1 = []int{4: 33, 22, 11, 1: 55, 66}
	s2 := []string{2: "c", 0: "a", "b"}
	fmt.Println(s1, len(s1))
	fmt.Println(s2, len(s2))

	var a1 = [...]int{4: 33, 22, 11, 1: 55, 66}
	a2 := [...]string{2: "c", 0: "a", "b"}
	fmt.Println(a1, len(a1))
	fmt.Println(a2, len(a2))
}
[0 55 66 0 33 22 11] 7
[a b c] 3
[0 55 66 0 33 22 11] 7
[a b c] 3

② 比较

  1. 数组,切片能否比较?
  • 数组:等长同类型数组可以比较,普通数组不能和nil比较,指针数组可以
  • 切片:不能比较
  1. DeepEqual()bytes.Equal()
  • 同样的申明方式,DeepEqual()bytes.Equal()无差别

  • 不同样的申明方,bytes.Equal()准确

var b1 []byte  // 仅变量申明,没有初始化
b2 := []byte{} // 字面量方式,自动调用了make函数初始化
fmt.Println(reflect.DeepEqual(b1, b2)) //false
//
v1 := reflect.ValueOf(b1)
v2 := reflect.ValueOf(b2)
fmt.Println(v1.IsNil(), v2.IsNil())
//
fmt.Println(bytes.Equal(b1, b2)) //true
  1. DeepEqual()==
  • DeepEqual 源码
func DeepEqual(x, y any) bool {
    // 判断空
	if x == nil || y == nil {
		return x == y
	}
	v1 := ValueOf(x)
	v2 := ValueOf(y)
    // 比较类型
	if v1.Type() != v2.Type() {
		return false
	}
	return deepValueEqual(v1, v2, make(map[visit]bool))
}
// visited递归时防止重复比较,比如后面例子中的循环链表
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool { ... }
  • DeepEqual 源码可知,DeepEqual 判断顺序是空值 -> 类型 -> 值
var m map[string]int
var n map[string]int

fmt.Println(m == nil) //true
fmt.Println(n == nil) //true
//不能通过编译,map不能比较
//fmt.Println(m ==n)

m = make(map[string]int, 10)
n = make(map[string]int, 100)
for s, i := range m {
    fmt.Println(s, i)
}
m["a"] = 1
n["a"] = 1

fmt.Println(reflect.DeepEqual(m, n)) //true
  • == 只重视表面的值,如
    • 指针,只对比地址
    • 结构体只比对内部的之,不管类型
// 结构体
type person struct {
    name string
}
p1 := person{"test"}
p2 := struct {
    name string
}{"test"}
fmt.Println(p1 == p2)                  //true
fmt.Println(reflect.DeepEqual(p1, p2)) //false
// 指针
x := new(int)
y := new(int)
fmt.Println("x==y:", x == y)                             //x==y: false
fmt.Println("DeepEqual(x, y):", reflect.DeepEqual(x, y)) //DeepEqual(x, y): true
// 循环链表
type link struct {
    data interface{}
    next *link
}
var a, b, c link
a.next = &b
b.next = &c
c.next = &a

fmt.Println(a == b)                  //false
fmt.Println(reflect.DeepEqual(a, b)) //true

③ Map

1. 问题: 哪些类型可以作为map的key?浮点型作为map的key有什么问题?

只有可比较类型才能做map的key(除了slice map function),当然还有含不可比较类型的数组和结构体

所以slice不能作为map的key

func main() {
    a1 := [3]int{}
    a2 := [3]int{}
    fmt.Println(a1 == a2) //true

    a3 := [3][]int{} // 数组中包含了不可比较的切片类型
    a4 := [3][]int{}
    fmt.Println(a3 == a4) //编译不通过
}

浮点型作为map的key时会自动调用math.Float64bits,可能导致值被覆盖

func main() {
	
	
	m := map[float64]int{}
	m[1.1] = 1
	m[1.2] = 2
	m[0.3] = 5 // 
	m[0.30000000000000001] = 6
	fmt.Printf("m:%+v\n", m) // math.Float64bits后,0.3 = 0.30000000000000001,所以m[0.3]会被覆盖为6
	
	fmt.Println(math.Float64bits(0.3))
	fmt.Println(math.Float64bits(0.30000000000000001))

}
m:map[0.3:6 1.1:1 1.2:2]
4599075939470750515
4599075939470750515

math.NAN【表示 “Not a Number”(不是一个数字)】。这个概念在数学中用来表示一个无法表示或计算的值,或者是一个无法定义的数,math.NaN() 函数的返回值类型为 float64)

func main() {
    var x, y float64
    x = math.NaN()
    y = 123.456
    fmt.Println(x == y) // false
    fmt.Println(x == x) // false
    fmt.Println(y == y) // true
}
func main() {

	m := map[float64]int{}
	m[math.NaN()] = 3
	m[math.NaN()] = 4
	fmt.Println("m[math.NaN]=", m[math.NaN()])
	fmt.Println("math.NaN() == math.NaN()?", math.NaN() == math.NaN())
	for k, v := range m {
		fmt.Println("k:", k, "v:", v)
	}
}
m[math.NaN]= 0
math.NaN() == math.NaN()? false
k: NaN v: 3
k: NaN v: 4

指针作为key时要注意地址问题

func main() {

	p1 := new(int)
	p2 := new(int)
	fmt.Println(p1, p2)
	//
	m1 := map[*int]int64{}
	m1[p1] = 1
	m1[p2] = 2
	fmt.Printf("m1:%v \n", m1)
}
0xc000012028 0xc000012040
m1:map[0xc000012028:1 0xc000012040:2]

2. 空struct()作用

  1. 不占空间
  2. 配合map实现集合类型(只查找元素是否存在)
// 不占空间
st := struct-demo{}{} 
fmt.Println("unsafe.Sizeof.st:", unsafe.Sizeof(st)) //unsafe.Sizeof.st: 0

set1 := map[int]struct{}{1: {}}
set2 := map[int]bool{1: false}
fmt.Println("set1.size:", unsafe.Sizeof(set1[1]), "set2.size:", unsafe.Sizeof(set2[1])) 
//set1.size: 0 set2.size: 1
// 配合map实现集合类型
package set

type Set map[int]struct{}

func (s Set) Put(x int) {
	s[x] = struct{}{}
}
func (s Set) Has(x int) (exists bool) {
	_, exists = s[x]
	return
}
func (s Set) Remove(val int) {
	delete(s, val)
}
s := make(set.Set)
s.Put(1)
s.Put(2)
fmt.Println(s.Has(1)) //true
s.Remove(1)
fmt.Println(s.Has(1)) //false

④ for range 遍历小细节

  • for range变例会创造副本,且每次遍历元素都会指向同一个地址
//错误写法
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
for key, val := range slice {
    m[key] = &val // 每次都是同一个val地址,所以当最后val=3时,m中所有key都为3
}

for k, v := range m {
    fmt.Println(k, "->", *v)
}
0 -> 3
1 -> 3                                                                 
2 -> 3                                                                 
3 -> 3  
  • 解决方法
// 解决方法
for key, val := range slice {
   value := val
	m[key] = &value
}
  • 同理,对副本的增减不会影响原来的值
slice := []int{0, 1, 2, 3}
for _, val := range slice {
    val++
}
fmt.Println(slice)

arr := [3]int{1, 2, 3}
for _, val := range arr {
    val++
}
fmt.Println(arr)
[0 1 2 3]
[1 2 3]  
  • 使用for range的小优化
    • 不使用副本
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
// 直接取slice地址
for key, _ := range slice {
    m[key] = &slice[key]
}

for k, v := range m {
    fmt.Println(k, "->", *v)
}
  • 对于直接循环指针类型,需要舍弃value
var ap *[3]int

会导致panic
//for i, p := range ap {
//	fmt.Println(i, p)
//}

//舍弃for range的第二个值
//不会导致panic
for i, _ := range ap {
    fmt.Println(i)
}

9️⃣ 双引号、单引号、反引号

  • rune是int32的别名,不是uint32的别名
  • byte是uint8的别名
  1. 默认单引号是rune类型
char1 := 'a'
fmt.Println(char1,reflect.TypeOf(char1)) // a int32
  1. 双引号支持转义,反引号不支持转移,单引号也不支持
s1 := "\142\"\143\"\144"
s2 := `\142\"\143\"\144`
s3 := '\142\"\143\"\144' // ❌

fmt.Println(s1) // b"c"d
fmt.Println(s2) // \142\"\143\"\144
fmt.Println(s3)
  1. ⭐️ ASCII进制的打印
//a,b,c的ASCII码值的十进制分别是97,98,99,对应的8进制为141,142,143
//对于字符串abc我们可以通过
s3 := "\141\142\143"
fmt.Println("s3:", s3, reflect.TypeOf(s3))

//a,b,c的ASCII码值的十进制分别是97,98,99,对应的16进制为61,62,63
s4 := "\x61\x62\x63"
fmt.Println("s4:", s4, reflect.TypeOf(s4))

//unicode也是通过16进制的码值表示的
s5 := "\u0061\u0062\u0063"
fmt.Println("s5:", s5, reflect.TypeOf(s5))
s6 := "\U00000061\U00000062\U00000063"
fmt.Println("s6:", s6, reflect.TypeOf(s6))
s3: abc string
s4: abc string
s5: abc string
s6: abc string
  • 双引号内
    • 单双引号16进制ASCII ✔
    • 单双引号8进制ASCII ❌
//单引号ASCII码值的八进制是47,十六进制是27
//双引号ASCII码值的八进制是42,十六进制是22
s7 := "'"
fmt.Println("s6:", s6, reflect.TypeOf(s7))
s8 := "\'" //编译不通过

s9 := ""
" //编译不通过
s10 := "\"" //需要加上转义符方可成为合法的字符串
//fmt.Println("s10:", s10, reflect.TypeOf(s10))

s11 := "\47" //编译不通过
s12 := "\42" //编译不通过

s12 := "\x22"
//fmt.Println("s12:", s12)
s13 := "\u0027"
//fmt.Println("s13:", s13)
s14 := "\U00000027"
//fmt.Println("s14:", s14)

1️⃣0️⃣ 字符串处理

  • TrimPrefix, TrimSuffix 匹配到一个就停止
  • Trim = TrimLeft + TrimRight, 贪婪模式,会找到不满足条件为止

请想想运行的结果

package main

import (
	"fmt"
	"strings"
)

func main() {
	var s = "gogo123go请想想运行的结果goabcgogo"
	p := fmt.Println
	p("1:", strings.TrimPrefix(s, "go")) 
	p("2:", strings.TrimSuffix(s, "go")) 
	p("3:", strings.TrimLeft(s, "go"))   
	p("4:", strings.TrimRight(s, "go"))  
	p("5:", strings.Trim(s, "go"))       
	p("6:", strings.TrimFunc(s, func(r rune) bool {
		return r < 128 // trim all ascii chars
	})) 
}
	p("1:", strings.TrimPrefix(s, "go")) // go123go请想想运行的结果goabcgogo
	p("2:", strings.TrimSuffix(s, "go")) // gogo123go请想想运行的结果goabcgo
	p("3:", strings.TrimLeft(s, "go"))   // 123go请想想运行的结果goabcgogo
	p("4:", strings.TrimRight(s, "go"))  // gogo123go请想想运行的结果goabc
	p("5:", strings.Trim(s, "go"))       // 请想想运行的结果

#️⃣ 参考资料

说明:文中代码主要来源于

  • 40
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值