Golang basic_leamingM 2023 1

Posted 知其黑、受其白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Golang basic_leamingM 2023 1相关的知识,希望对你有一定的参考价值。

阅读目录

数据定义

函数返回值问题

1 下面代码是否可以编译通过?

package main

/*
    下面代码是否编译通过?
*/
func myFunc(x,y int)(sum int,error)
    return x+y,nil


func main() 
    num, err := myFunc(1, 2)
    fmt.Println("num = ", num)

答案:编译报错理由

# command-line-arguments
./test1.go:6:21: syntax error: mixed named and unnamed function parameters

考点:函数返回值命名

结果:编译出错。

在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。

如果返回值有多个返回值必须加上括号;

如果只有一个返回值并且有命名也需要加上括号;

此处函数第一个返回值有 sum 名称,第二个未命名,所以错误。

可以这么写:

package main

import "fmt"

/*
   下面代码是否编译通过?
*/
func myFunc(x, y int) (sum int, err error) 
	return x + y, nil


func main() 
	num, err := myFunc(1, 2)
	if err != nil 
		fmt.Println("错误:", err)
	
	fmt.Println("num = ", num)

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
num =  3
PS E:\\TEXT\\test_go\\one>

切片

1 切片是引用数据类型 – 注意切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

package main

import "fmt"

func myFunc() 
	// a := [8]int0, 1, 2, 3, 4, 5, 6, 7
	// s1 := a[:5] // [0 1 2 3 4]
	// s2 := a[3:6] // [3 4 5]


func Zi() 
	s1 := make([]int, 3)
	//[0 0 0]

	s2 := s1
	//将s1 直接赋值给s2,s1 和s2 共用一个底层数组

	s2[0] = 100
	fmt.Println(s1)
	//[100 0 0]

	fmt.Println(s2)
	//[100 0 0]


func main() 
	myFunc()
	Zi()

结构体比较问题

1 下面代码是否可以编译通过?

为什么?

package main

import "fmt"

func main() 

	sn1 := struct 
		age  int
		name string
	age: 11, name: "qq"

	sn2 := struct 
		age  int
		name string
	age: 11, name: "qq"

	if sn1 == sn2 
		fmt.Println("sn1 == sn2")
	

	sm1 := struct 
		age int
		m   map[string]string
	age: 11, m: map[string]string"a": "1"

	sm2 := struct 
		age int
		m   map[string]string
	age: 11, m: map[string]string"a": "1"

	if sm1 == sm2 
		fmt.Println("sm1 == sm2")
	

结果:编译不通过

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:31:5: invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)
PS E:\\TEXT\\test_go\\one>

考点:结构体比较

结构体比较规则注意1:

只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

比如:顺序不一致报错

sn1 := struct 
	age  int
	name string
age: 11, name: "qq"

sn3:= struct 
    name string
    age  int
age:11, name:"qq"


sn3sn1 就不是相同的结构体了,不能比较。

结构体比较规则注意2:

结构体是相同的,但是结构体属性中有不可以比较的类型,如 map,slice,则结构体不能用 == 比较。

可以使用 reflect.DeepEqual 进行比较:

package main

import (
	"fmt"
	"reflect"
)

func main() 

	sm1 := struct 
		age int
		m   map[string]string
	age: 11, m: map[string]string"a": "1"

	sm2 := struct 
		age int
		m   map[string]string
	age: 11, m: map[string]string"a": "1"

	if reflect.DeepEqual(sm1, sm2) 
		fmt.Println("sm1 == sm2")
	 else 
		fmt.Println("sm1 != sm2")
	

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
sm1 == sm2
PS E:\\TEXT\\test_go\\one>

string 与 nil 类型

1 下面代码是否能够编译通过?

为什么?


考点:函数返回值类型

答案:编译不会通过。

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:11:9: cannot use nil as string value in return statement
PS E:\\TEXT\\test_go\\one>

分析:

nil 可以用作 interface、function、pointer、map、slice 和 channel 的 “空值”

但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。通常编译的时候不会报错,但是运行是时候会报: cannot use nil as type string in return argument

常量

1 下面函数有什么问题?



解析考点:常量
常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。

内存四区概念:

A 数据类型本质:固定内存大小的别名。

B 数据类型的作用:编译器预算对象(变量)分配的内存空间大小。


流程说明

1、操作系统把物理硬盘代码 load 到内存。

2、操作系统把 c 代码分成四个区。

3、操作系统找到 main 函数入口执行。

栈区(Stack):

空间较小,要求数据读写性能高,数据存放时间较短暂。

由编译器自动分配和释放,存放函数的参数值、函数的调用流程方法地址、局部变量等(局部变量如果产生逃逸现象,可能会挂在在堆区)

堆区(heap):

空间充裕,数据存放时间较久。

一般由开发者分配及释放(但是Golang中会根据变量的逃逸现象来选择是否分配到栈上或堆上),启动Golang的GC由GC清除机制自动回收。

全局区-静态全局变量区

全局变量的开辟是在程序在main之前就已经放在内存中。

而且对外完全可见。

即作用域在全部代码中,任何同包代码均可随时使用,在变量会搞混淆,而且在局部函数中如果同名称变量使用 := 赋值会出现编译错误。

全局变量最终在进程退出时,由操作系统回收。我们在开发的时候,尽量减少使用全局变量的设计。

全局区-常量区:

常量区也归属于全局区,常量为存放数值字面值单位,即不可修改。或者说的有的常量是直接挂钩字面值的。

比如:

const cl = 10

cl 是字面量 10 的对等符号。

所以在golang中,常量是无法取出地址的,因为字面量符号并没有地址而言。

数组和切片

切片的初始化与追加

package main

import (
	"fmt"
)

func main() 
	s := make([]int, 10)

	s = append(s, 1, 2, 3)

	fmt.Println(s)
	// [0 0 0 0 0 0 0 0 0 0 1 2 3]

考点:切片追加, make 初始化均为 0。

slice 拼接问题


编译失败

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:8:18: cannot use s2 (variable of type []int) as type int in argument to append
PS E:\\TEXT\\test_go\\one>

两个 slice 在 append 的时候,记住需要进行将第二个 slice 进行 ... 打散再拼接。

package main

import "fmt"

func main() 
	s1 := []int1, 2, 3
	s2 := []int4, 5
	s1 = append(s1, s2...)
	fmt.Println(s1)

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
[1 2 3 4 5]
PS E:\\TEXT\\test_go\\one>

slice 中 new 的使用

package main

import "fmt"

func main() 

	list := new([]int)
	list = append(list, 1)
	fmt.Println(list)

编译失败:

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
# command-line-arguments
.\\test1.go:9:16: first argument to append must be a slice; have list (variable of type *[]int)
PS E:\\TEXT\\test_go\\one>

分析:

  • 切片指针的解引用。
  • 可以使用 list:=make([]int,0) list 类型为切片。
  • 或使用 *list = append(*list, 1) list 类型为指针。

new 和 make 的区别

二者都是内存的分配(堆上)。

make 只用于 slice、map 以及 channel 的初始化(非零值);

而 new 用于类型的内存分配,并且内存置为零。

所以在我们编写程序的时候,就可以根据自己的需要很好的选择了。

make 返回的还是这三个引用类型本身;

而 new 返回的是指向类型的指针。

Map

Map 的 Value 赋值

编译失败,
./test7.go:18:23: cannot assign to struct field list["student"].Name in map

分析

map[string]Student 的 value 是一个 Student 结构值,所以当list["student"] = student,是一个值拷贝过程。

list["student"] 则是一个值引用。

那么值引用的特点是只读。

所以对 list["student"].Name = "LDB" 的修改是不允许的。

方法一

package main

import "fmt"

type Student struct 
	Name string


var list map[string]Student

func main() 

	list = make(map[string]Student)

	student := Student"Aceld"

	list["student"] = student
	//list["student"].Name = "LDB"

	/*
	   方法1:
	*/
	tmpStudent := list["student"]
	tmpStudent.Name = "LDB"
	list["student"] = tmpStudent

	fmt.Println(list["student"]) // LDB

其中

/*
  方法1:
*/
tmpStudent := list["student"]
tmpStudent.Name = "LDB"
list["student"] = tmpStudent

是先做一次值拷贝,做出一个 tmpStudent 副本,然后修改该副本,然后再次发生一次值拷贝复制回去,list["student"] = tmpStudent,但是这种会在整体过程中发生2次结构体值拷贝,性能很差。

方法二

package main

import "fmt"

type Student struct 
	Name string


var list map[string]*Student

func main() 

	list = make(map[string]*Student)

	student := Student"Aceld"

	list["student"] = &student
	list["student"].Name = "LDB"

	fmt.Println(list["student"]) // &LDB

我们将 map 的类型的 value 由 Student 值,改成 Student 指针。

var list map[string]*Student

这样,我们实际上每次修改的都是指针所指向的 Student 空间,指针本身是常指针,不能修改,只读属性,但是指向的 Student 是可以随便修改的,而且这里并不需要值拷贝。

只是一个指针的赋值。

map 的遍历赋值

package main

import (
	"fmt"
)

type student struct 
	Name string
	Age  int


func main() 
	//定义map
	m := make(map[string]*student)

	//定义student数组
	stus := []student
		Name: "zhou", Age: 24,
		Name: "li", Age: 23,
		Name: "wang", Age: 22,
	

	//将数组依次添加到map中
	for _, stu := range stus 
		m[stu.Name] = &stu
	

	//打印map
	for k, v := range m 
		fmt.Println(k, "=>", v.Name)
	

错误结果

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
zhou => wang
li => wang
wang => wang
PS E:\\TEXT\\test_go\\one>

map 中的 3 个 key 均指向数组中最后一个结构体。

分析

foreach 中,stu 是结构体的一个拷贝副本,所以 m[stu.Name]=&stu实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个 struct 的值拷贝。

正确写法 1

package main

import (
	"fmt"
)

type student struct 
	Name string
	Age  int


func main() 
	//定义map
	m := make(map[string]*student)

	//定义student数组
	stus := []student
		Name: "zhou", Age: 24,
		Name: "li", Age: 23,
		Name: "wang", Age: 22,
	

	// 遍历结构体数组,依次赋值给map
	for i := 0; i < len(stus); i++ 
		m[stus[i].Name] = &stus[i]
	

	//打印map
	for k, v := range m 
		fmt.Println(k, "=>", v.Name)
	

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
zhou => zhou
li => li
wang => wang
PS E:\\TEXT\\test_go\\one>

正确写法 2

package main

import "fmt"

type student struct 
	Name string
	Age  int


func main() 
	//定义map
	m := make(map[string]*student)

	//定义student数组
	stus := []student
		Name: "zhou", Age: 24,
		Name: "li", Age: 23,
		Name: "wang", Age: 22,
	

	//将数组依次添加到map中
	for key, _ := range stus 
		m[stus[key].Name] = &stus[key]
	

	//打印map
	for k, v := range m 
		fmt.Println(k, "=>", v.Name)
	

interface

interface 的赋值问题

继承与多态的特点,在golang中对多态的特点体现从语法上并不是很明显。

我们知道发生多态的几个要素:

1、有 interface 接口,并且有接口定义的方法。
2、有子类去重写 interface 的接口。
3、有父类指针指向子类的具体对象。

那么,满足上述3个条件,就可以产生多态效果,就是,父类指针可以调用子类的具体方法。

所以上述代码报错的地方在 var peo People = Stduent 这条语句, Student 已经重写了父类 People 中的 Speak(string) string 方法,那么只需要用父类指针指向子类对象即可。

所以应该改成 var peo People = &Student 即可编译通过。
(People为interface类型,就是指针类型)

package main

import (
	"fmt"
)

type People interface 
	Speak(string) string


type Stduent struct

func (stu *Stduent) Speak(think string) (talk string) 
	if think == "love" 
		talk = "You are a good boy"
	 else 
		talk = "hi"
	
	return


func main() 
	var peo People = &Stduent
	think := "loves"
	fmt.Println(peo.Speak(think))

PS E:\\TEXT\\test_go\\one> go run .\\test1.go
hi
PS E:\\TEXT\\test_go\\one>

interface 的内部构造(非空接口 iface 情况)


分析:

我们需要了解 interface 的内部结构,才能理解这个题目的含义。

interface 在使用的过程中,共有两种表现形式:

1 一种为空接口(empty interface),定义如下:

var MyInterface interface

2 非空接口(non-empty interface), 定义如下:

type MyInterface interface 
  function()

这两种 interface 类型分别用两种struct表示,空接口为eface, 非空接口为iface.

空接口 eface

空接口 eface 结构,由两个属性构成,一个是类型信息 _type,一个是数据信息。

其数据结构声明如下:

type eface struct       //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer 
    //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)

_type属性:

是GO语言中所有类型的公共描述,Go语言几乎所有的数据结构都可以抽象成 _type,是所有类型的公共描述,type负责决定data应该如何解释和操作,type的结构代码如下:

type _type struct 
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff

data属性:
表示指向具体的实例数据的指针,他是一个 unsafe.Pointer 类型,相当于一个 C 的万能指针 void*。

非空接口 iface

iface 表示 non-empty interface 的数据结构,非空接口初始化的过程就是初始化一个iface类型的结构,其中data的作用同eface的相同,这里不再多加描述。

type iface struct 
  tab  *itab
  data unsafe.Pointer

iface结构中最重要的是 itab 结构(结构如下),每一个 itab 都占 32 字节的空间。

itab可以理解为pair<interface type, concrete type> 。

itab里面包含了interface的一些关键信息,比如method的具体实现。

type itab struct 
  inter  *interfacetype   // 接口自身的元信息
  _type  *_type           // 具体类型的元信息
  link   *itab
  bad    int32
  hash   int32            // _type里也有一个同样的hash,此处多放一个是为了方便运行接口断言
  fun    [1]uintptr       // 函数指针,指向具体类型所实现的方法

所以,People拥有一个Show方法的,属于非空接口,People的内部定义应该是一个iface结构体。

type People interface 
    ShowGolang basic_leamingM 2023 1

Golang basic_leamingM 2023 3 topic

Golang basic_leamingM 2023 2 theory

golang:发布订阅系统

魔兽兄弟 调试逆向

二分查找:如何快速定位IP对应的省份地址?