go语言学习

一、基础语法

1.变量定义

package main

import "fmt"

func main(){
	//定义变量:var
	//常量定义:const

	//01-先定义变量,再赋值, var 变量名 数据类型
	var name string
	name = "chenran"

	var age int
	age = 20

	fmt.Println("name",name)
	fmt.Printf("name is: %s, age is:%d\n", name,age)

	//02-定义时直接赋值
	var gender = "man"
	fmt.Println("gender:" ,gender)

	//03-定义直接赋值,使用自动推导,(最常用)
	address := "成都"
	fmt.Println("address:", address)

	//04-平行赋值
	i, j := 10,20
	fmt.Println("i:", i , "j:",j)
	i, j = j, i
	fmt.Println("i:", i , "j:",j) 

}

2.基础数据类型

int, int8,int16, int32, int64

uint ...uint64

float32, float64

true/false

3.自增语法

i++, i-- ,自增语法必须单独一行

package main

import "fmt"

func main(){
	i := 20
	i++
	fmt.Println("i:",i)
}

4.指针

package main

import "fmt"

func main(){
	//使用指针时会使用垃圾回收机制(gc:garbage collector),开发人员不需要手动释放内存
	//可以返回栈上的指针,编译时就确定了变量的分配位置
	//编译的时候,如果发现有必要的话,就将变量分配到堆上

	name := "lily"
	ptr := &name
	fmt.Println("name:", name)
	fmt.Println("name ptr:", ptr)

	//02-使用new关键字定义,提前定义一个指针,对指针赋值
	name2ptr := new(string)
	*name2ptr = "duck"
	fmt.Println("name2:", *name2ptr)
	fmt.Println("name2 ptr:", name2ptr)

	//可以返回栈上的指针,编译器编译程序时,会自动判断这段代码,将city变量分配到堆上
	res := testptr()
	fmt.Println("res city:", *res, "address:", res)

    //空指针:nil
	//if两端不用加(),加{}
	if res == nil{
		fmt.Println("res是空,nil")
	}else{
		fmt.Println("res是非空")
	}
}

//定义一个函数,返回一个string类型指针,go语言返回写在参数列表后面
func testptr() *string {
	city := "上海"
	ptr := &city
	return ptr

}

5.string

package main

import "fmt"

func main(){
	//01-定义
	name := "duck"

	//需要换行,原生输出字符串时,使用反引号``
	usage := `./a.out <option>
		-h help
		-a xxxx`

	fmt.Println("name:",name)
	fmt.Println("usage:",usage)

	//02-长度,访问
	//string没有length方法,可以使用自由函数len()
	ll := len(name)
	fmt.Println("ll:",ll)

	//不需要加()
	for i :=0; i < len(name); i++{
		fmt.Printf("i: %d, v: %c\n",i, name[i])
	}

	//03-拼接
	i, j := "hello","world"
	fmt.Println("i+j=", i+j)

	//使用const修饰为常量,不能修改
	const address = "beijing"
	//address = ’上海‘
	fmt.Println("address:", address)

}

*6 数组和切片(Go 语言切片面试真题 8 连问

1、数组和切片的区别

  • a.数组是固定长度,不能动态扩容,在编译期就确定大小。
  • b.切片是对数组的抽象(动态数组),切片长度不固定,可以追加元素。切片不是数组,切片描述的是一块数组。切片可以使用append追加元素,当cap不足时动态扩容。

2、拷贝大切片代价比拷贝小切片代价大吗?
不会。
切片本质内部结构如下:如果发生拷贝,本质上就是拷贝下面的三个字断。

type SliceHeader struct {
 Data uintptr
 Len  int
 Cap  int
}


3、深拷贝和浅拷贝
本质区别:复制出来的对象与原对象是否会指向同一地址

切片拷贝三种方式:

  • a.使用“=”拷贝,浅拷贝
  • b.使用“[:]”下标的方式复制切片,浅拷贝
  • c.使用内置函数copy()进行切片拷贝,深拷贝

4、零切片、空切片、nil切片是什么

  • 零切片:切片内部数组都是零值或者底层数组的内容全是nil的切片;使用make创建的、长度、容量都不为零的切片就是零切片。
  • nil切片:nil切片的长度和容量都为0,且和nil比较的结果都为true;采用直接创建切片的方式、new创建切片的方式都可以创建nil切片。
  • 空切片:空切片的长度和容量都是0,但是和nil的比较结果是false,因为所有空切片的数据指针都是指向同一个地址;使用字面量、make可以创建空切片。


5、切片的扩容策略
切片在扩容时会进行内容对齐,这个和内存分配策略有关。当原slice容量小于1024时,新slice容量变为原来的2倍;当原slice容量超过1024时,新slice容量变成原来的1.25倍。

6、参数传递切片和切片指针有什么区别?
当切片作为参数传递时,其实就是一个结构体的传递,因为Go语言参数传递只有值传递,传递一个切片就会浅拷贝原切片,但因为底层数据的地址没有改变,所以在函数内对切片的修改,也会影响到函数外的切片。​​​​​​​
例外:如果指向底层数组的指针被覆盖或者修改(copy、重分配、append触发扩容),此时函数内部对数据的修改将不再影响到外部的切片,代表长度的len和容量cap也均不会被修改。因为函数外的切片已经指向了一个新的底层数组。

func modifySlice(s []string)  {
 s[0] = "song"
 s[1] = "Golang"
 fmt.Println("out slice: ", s)
}

func main()  {
 s := []string{"asong", "Golang梦工厂"}
 modifySlice(s)
 fmt.Println("inner slice: ", s)
}
// 运行结果
out slice:  [song Golang]
inner slice:  [song Golang]


//特例
func appendSlice(s []string)  {
 s = append(s, "快关注!!")
 fmt.Println("out slice: ", s)
}

func main()  {
 s := []string{"asong", "Golang梦工厂"}
 appendSlice(s)
 fmt.Println("inner slice: ", s)
}
// 运行结果
out slice:  [asong Golang梦工厂 快关注!!]
inner slice:  [asong Golang梦工厂]

7、range遍历切片有什么要注意
range关键字用于for循环中迭代数组(array)、切片(slice)、通道(channel)、集合(map)的元素。两种使用方式:
使用range遍历切片时会先拷贝一份,然后再遍历拷贝数据。
​​​​​​​面试官:go中for-range使用过吗?这几个问题你能解释一下原因吗?

for k,v := range _ { } //遍历下标和对应值。
for k := range _ { } //只遍历下标

6.定长数组

package main

import "fmt"

func main(){

	//1-定义一个具有10个数字的数组
	//nums := [10]int{1,2,3,4}
	//var nums = [10]int{1,2,3,4}

	nums := [10]int{1,2,3,4,5}

	//2-遍历
	for i :=0; i<len(nums); i++{
		fmt.Println("i:", i, "j:", nums[i])
	}

	//方式二:for range ===>python支持
	//value全程是一个临时变量,不断重新赋值,修改它不会修改原始数组
	for key,value := range nums{
		fmt.Println("key:",key, "value:",value)
	}
	
	//想忽略一个值,可以使用_
	//如果想忽略两个,不能使用:=,使用=, 有:表示两边有新东西
	for _, value := range nums{
		fmt.Println("忽略key,value:",value)
	}
	
	//不定长数组
	//3-使用make创建数组
}

7.不定长数组(切片,slice)

切片1

package main

import "fmt"

func main(){
	//切片:slice,它的底层是数组,可以动态改变长度
	//定义一个切片,包含多个地名
	//names := []string{"北京","上海","广州","深圳"}
	names := []string{"北京","上海","广州","深圳"}

	for i ,v := range names{
		fmt.Println("i:",i, "v:",v)
	}

	//1.追加数据
	fmt.Println("追加元素之前,names的长度:",len(names),",容量:",cap(names))
	names = append(names,"海南")
	fmt.Println("names追加元素后赋值给自己:",names)
	fmt.Println("追加元素之后,names的长度:",len(names),",容量:",cap(names))

	//2.对于一个切片,不仅有”长度“的概念len(),还有"容量"的概念cap()
	nums := []int{}
	for i := 0; i<50; i++{
		nums = append(nums, i)
		fmt.Println("len:",len(nums),",cap:",cap(nums))
	}

}

1.可以使用append追加数据

2.len获取长度,cap获取容量

3.如果容量不足,再次追加数据时。会动态分配2倍空间

切片2

(make函数,copy函数)

package main

import "fmt"

func main() {
	names := [7]string{"北京","上海","广州","深圳","洛阳","南京","秦皇岛"}

	//基于names创建一个新的数组
	names1 := [3]string{}
	names1[0] = names[0]
	names1[1] = names[1]
	names1[2] = names[2]
	fmt.Println("names1:",names1)

	//切片可以基于一个数组,灵活的创建新的数组
	names2 := names[0:3]  //左闭右开
	fmt.Println("names2:",names2)

	names2[2] = "hello"
	fmt.Println("修改names[2]之后,name2:", names2)
	fmt.Println("修改names[2]之后,name:", names)

	//1.如果从0元素开始截取,那么冒号左边的数字可以省略
	names3 := names[:5]
	fmt.Println("names3:",names3)

	//2.如果截取到数组最后一个元素,,那么冒号右边数字可以省略[5:]
	//3.如果想从左至右全部使用,那冒号左右两边的数字可以省略[:]

	//4.也可以基于一个字符串进行切片截取:取字符串的子串 helloworld
	sub1 := "helloworld"[5:7]
	fmt.Println("sub1:", sub1)  //'wo'

	//5.可以在创建空切片的时候,明确指定切片的容量,使用make函数
	str2 := make([]string, 3, 20)
	fmt.Println("str2: len:",len(str2),", cap:", cap(str2))
	str2[0] = "hello"
	str2[1] = "world"

	//6.若想切片完全独立于原来数组,使用copy深拷贝,copy函数接收的参数类型时切片,所以需要使用[:]将数组变成切片
	namesCopy := make([]string, len(names))
	copy(namesCopy,names[:])
	namesCopy[0] = "香港"
	fmt.Println("namescopy:",namesCopy)
	fmt.Println("names:",names)

}

8.字典(map)

哈希表,key->value,存储的key是经过哈希运算的

新手异常:没有分配空间

package main

import "fmt"

func main(){
	//定义一个字典
	var idNames map[int]string  //定义一个map,此时这个map是不能直接赋值的,他是空的

	idNames[0] = "duke"
	idNames[1] = "lily"

	for key, value := range idNames{
		fmt.Println("key:",key,"value:",value)
	}
}

 2.使用map之前,一定要手动分配空间(make函数)

package main

import "fmt"

func main(){
	//定义一个字典
	var idNames map[int]string  //定义一个map,此时这个map是不能直接赋值的,他是空的

	//2.使用make函数分配空间,可以不指定长度,但建议直接指定长度,性能更好
	idNames = make(map[int]string,10)
	idNames[0] = "duke"
	idNames[1] = "lily"

	//3.定义时直接分配空间
	//idNames1 := make(map[int]string,10) //最常用方法
	idScore := make(map[int]float64)

	//4.遍历map
	for key, value := range idNames{
		fmt.Println("key:",key,"value:",value)
	}

	//5.如何确定一个key是否存在在map中
	//在map中不存在访问越界的问题,它任务所有的key都是有效的,所以访问一个不存在的key不会崩溃,返回这个类型的零值
	//零值:bool=》false, 数字=》0, 字符串=》空
	name9 := idNames[9]
	fmt.Println("name9:",name9)  //空
	fmt.Println("idScore[100]:",idScore[100]) //0

	//无法通过获取value来判断一个key是否存在,因此需要一个有效校验key是否存在
	value, ok := idNames[1] //如果id=1存在,则value就是key=1对应值,ok返回true反之返回0值
	if ok {
		fmt.Println("id=1这个key是存在的,value为:",value)
	}

	value, ok = idNames[10]
	if ok {
		fmt.Println("id=10这个key是存在的,value为:",value)
	}else{
		fmt.Println("id=10这个key是不存在的,value为:",value)
	}

	//6. 删除map中的元素,使用自由函数delete来删除指定的key
	fmt.Println("idNames删除前:",idNames)
	delete(idNames, 1)
	delete(idNames,  100)
	fmt.Println("idNames删除后:",idNames)

	//多个并发处理时,需要对map上锁
}

9.函数 

快捷键:将鼠标置于函数上后 ctrl+shift+i  快速查看函数

package main

import "fmt"

//1.函数返回值在参数列表之后
//2.如果有多个返回值,需要使用圆括号包裹,多个参数之间使用逗号分隔
func test2(a int ,b int, c string )(int,string,bool)  {
	return a + b ,c , true
}

func test3(a, b int, c string )(res int,str string,bl bool)  {
	var i,j,k int
	i = 1
	j = 2
	k = 3
	fmt.Println(i,j,k)

	//直接使用返回值的变量名字参与运算
	res = a + b
	str = c
	bl = true

	//当返回值有名字的时候,可以直接简写return
	return

	//return a + b ,c , true

}

//如果返回值只有一个参数,并且没有名字,那么不需要圆括号
func test4() int{
	return 10
}

func main(){
	v1,s1,_ := test2(10, 20, "hello")
	fmt.Println("v1:",v1,"s1:",s1)

	v3, s3, _ := test3(20,30,"world")
	fmt.Println("v3:",v3, ", s3:",s3)

}

10.内存逃逸

package main

import "fmt"

//内存逃逸,原本是栈上的,逃到堆上去了

func main(){
	p1 := testPtr1()
	fmt.Println("p1:",*p1)
}

//定义一个函数,返回一个string类型指针,go语言返回写在参数列表后面:wq:wq

func testPtr1() *string {
	name := "Duke"  //自己占用的,没有逃逸
	p0 := &name
	fmt.Println("*p0:", *p0) 

	city := "上海"  //逃逸
	ptr := &city
	return ptr
}

1.编译并将错误信息输出

$ go build -o test.exe  --gcflags "-m -m -l" 11.内存逃逸.go > 1.txt 2>&1
$ grep -E "name|city" 1.txt --color

11.import

1.创建目录结构

add.go

package add

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

//如果一个包里的函数想对外提供访问权限,那么首字母一定要大写

sub.go

package sub

func Sub(a,b int) int{
	return a - b
}

main.go

package main

import (
	"12-import/add"
	SUB "12-import/sub" //SUB相当于重命名,python中as
	//"12-import/sub" //sub是文件名,同时也是包名
	"fmt"
)

func main(){
	res := SUB.Sub(20,10) //包名.函数去调用函数
	fmt.Println("sub.sub(20, 10)=",res )

	res2 := add.Add(20,10)
	fmt.Println("add.Add(20,10)=", res2)
}

报错:package command-line-arguments is not a main package

原因:把package _2_import 修改为 package main就好了

二、基础语法补充

1.switch

package main

import (
	"fmt"
	"os"
)

//从命令输入参数,在switch中进行处理

func main(){
	//go: os.Args ==> 直接获取命令的输入,是一个字符串切片
	cmds := os.Args

	//os.Args[0] ==> 程序名
	//os.Args[1] ==> 第一个参数,依次类推
	for key, cmd := range cmds{
		fmt.Println("key:",key, "cmd:",cmd, "cmds len:", len(cmds))
	}

	if len(cmds) < 2{
		fmt.Println("请正确输入参数!")
		return
	}

	switch cmds[1] {
	case "hello":
		fmt.Println("hello")
		// go 的switch,默认加上了break了,不需要手动处理
		//若想向下穿透的话,需要加上关键字:fallthrough
		fallthrough
	case "world":
		fmt.Println("world")
	default:
		fmt.Println("default called")
	}
}

2.标签与continue-goto-break配合使用

package main

import "fmt"

func main() {
	//标签 LABLEL1
	//goto LABLEL1  ==>(goto可以任意跳一个位置)。下次进入循环的时候,i不会保存之前的状态,重新从0开始计算,重新来过。
	//continue LABLEL1 ==>continue会跳到指定位置,但是会记录之前的状态,i变成1
	//break LABLEL1  ==>直接跳出指定位置的循环

	//标签名字是自定义
LABEL123:
	for i := 0; i < 5; i++ {
		for j := 1; j < 5; j++ {
			if j == 3 {
				//goto LABEL123
				//continue LABEL123
				break LABEL123
			}
			fmt.Println("i:",i, "j:",j)
		}
	}
	fmt.Println("over!")
}

3.枚举 const+iota

package main

import "fmt"

//在go语言中没有枚举类型,但是可以用const+iota(常量累加器)来进行模仿
//模拟一个一周枚举
const(
	MONDAY = iota 		//0
	TUESDAY = iota		//1
	WEDNESDAY = iota	//2
	THURSDAY			//3
	FRIDAY				//4
	SATURDAY			//5
	SUNDAY				//6
	M,N = iota,iota 	//7, 7 //const属于预编译期赋值,所以不需要:=进行自动推导
)

const (
	JAN = iota + 1 //1
	FEB 			//2
	MAR				//3
	API				//4
)

//1.iota是常数组计数器
//2.iota从0开始,每行递增1
//3.常量组有个特点,如果不赋值,默认与上一行表达式相同
//4.如果同一行出现两个iota,那么两个iota的值是相同的
//5.每个常量组的iota是独立的,如果遇到const iota会重新清零

func main(){
	fmt.Println("打印周")
	fmt.Println(MONDAY)
	fmt.Println(TUESDAY)
	fmt.Println(WEDNESDAY)
	fmt.Println(THURSDAY)
	fmt.Println(FRIDAY)
	fmt.Println(SATURDAY)
	fmt.Println(SUNDAY)
	fmt.Println("M:",M,"N:",N)

	fmt.Println("打印月")
	fmt.Println(JAN)
	fmt.Println(FEB)
	fmt.Println(MAR)
	fmt.Println(API)

}

4.结构体

go语言中,使用结构体模仿类

package main

import "fmt"

type MyInt int

//go语言中,使用type + struct来处理
type Student struct {
	name string
	age int
	gender string
	score float64
}

func main(){
	var i,j MyInt
	i,j = 10, 20
	fmt.Println("i+j:",i+j)

	//创建变量,并赋值
	lily := Student{
		name: "lily",
		age : 20,
		gender :"女",
		score: 80, 	//最后一个元素后面必须加上逗号,如果不加逗号则必须与}同一行
	}

	//使用结构体各个字段
	fmt.Println("lily:",lily.name, lily.age, lily.gender,lily.score)

	// ->
	s1:= &lily
	fmt.Println("lily 使用指针s1.name打印:",s1.name,s1.age,s1.score,s1.gender)
	fmt.Println("lily 使用指针(*s1).name打印:",(*s1).name,s1.age,s1.score,s1.gender)

	//在定义期间对结构体赋值时,如果每个字段都赋值了,那么字段的名字可以省略不写
	//如果只对局部变量赋值,那么必须明确指定变量名字
	Duke := Student{
		name: "duck",
		age : 28,
		//gender: "男",
		//score: 90,
	}
	fmt.Println("Duck:",Duke)
}

5.init函数

go语言中,自带init函数,每一个包可以包含一个或多个init函数

这个init会在包被引用的时候(import)进行自动调用

结构

sub.go

package sub

import "fmt"

//1.init函数没有参数,没有返回值,原型固定如下
//2.一个包中包含多个init时,调用顺序是不确定的(同一个包里的多个文件都可以)
//3.init函数不允许用户显式调用
//4.有时候引用一个包,可能只想使用这个包里的init函数(如:mysql的init对驱动进行初始化)
//但是不想使用这个包里面的其他函数,为了防止编译器报错,可以使用_形式来处理  //import _"xxx/xx/sub"

func init(){
	fmt.Println("this is first init() in package sub")
}

func init(){
	fmt.Println("this is second init() in package sub")
}

func Sub(a,b int) int{
	//init() ==> init不允许显式调用
	return a - b
}

main.go

package main

import (
	_"day02/05-init函数/sub" //加_,此时只会调用sub里面的init函数,编译不会出错
	//"fmt"
)

func main(){
	//res2 := sub.Sub(10,5)
	//fmt.Println("sub.Sub(10,5)=", res2)
}

6.defer(延迟)+ 文件读取

延迟,关键字,可以用于修饰语句,函数,确保这条语句可以在当前栈退出时执行,先入后出

lock.Lock()

a = "hello"

lock.Unlock()  ===>常常忘记解锁

go语言可以使用defer来解决

}  

        lock.Lock()

        defer lock.Unlock()   ==>在当前栈退出的时候(例如:函数结束时)

        a = "hello"

}

{

        f1,_ := file.open()

        defer f1.Close()

}

实例

package main

import (
	"fmt"
	"os"
)

func main(){
	//1.延迟,关键字,可以用于修饰语句,函数,确保这条语句可以在当前栈退出时执行
	//2.一般用于做资源清理工作
	//3.解锁,关闭文件
	//4.在同一个函数中多次调用defer,执行类似于栈的机制:先入后出

	filename := "01-switch.go"
	readFile(filename)
}

func readFile(filename string){
	//func Open(name string) (*File, error)
	//1.go语言一般会将错误码作为最后一个参数返回
	//2.err一般nil代表没有错误,执行成功,非nil表示执行失败

	f1, err := os.Open("01-switch.go")

	//匿名函数,没有名字,属于一次性 ==》lambda函数
	defer func(a int){
		fmt.Println("准备关闭文件!,code:", a)
		_ = f1.Close()
	}(100) //创建一个匿名函数,同时使用()调用

	defer f1.Close()
	if err != nil{
		fmt.Println("os.Open(\"01-switch.go\")==>打开文件失败,err:",err)
		return
	}

	defer fmt.Println("00000000")
	defer fmt.Println("11111111")
	defer fmt.Println("22222222")

	buf := make([]byte, 1024)
	n, _ := f1.Read(buf)
	fmt.Println("读取文件的实际长度:", n)
	fmt.Println("读取文件的内容:",string(buf))
}

结果 

三、类相关操作

go语言支持类操作,但是美哟class,使用结构体操作

1.类封装+绑定方法

package main

import "fmt"

//Person类,绑定方法:Eat, Run, Laugh, 成员

type Person struct {
	//成员属性
	name string
	age int
	gender string
	score float64
}

//在类外面绑定方法, ==》 func (p<当前函数名字> Person<类名>) Eat()<绑定的方法>{
//func (p Person) Eat(){
//	//fmt.Println("Person is eating")
//	//类的方法,可以使用自己的成员
//}

//使用指针
func(this *Person) Eat1(){
	//fmt.Println("Person is eating")
	//fmt.Println(this.name + " is eating")
	this.name = "chenran"
}

//使用非指针,操作的是副本
func(this Person) Eat2(){
	//fmt.Println("Person is eating")
	this.name = "chenran"
}

func main(){
	lily := Person{
		"lily",
		30,
		"女生",
		10,
	}

	lily1 := lily

	fmt.Println("eat1(),使用p *Person, 修改name的值 ...")
	fmt.Println("修改前lily:",lily) //lily
	lily.Eat1()
	fmt.Println("修改后lily:",lily) //chenran

	fmt.Println("eat2(),使用p Person, 但是不是指针 ...")
	fmt.Println("修改前lily1:",lily1)  //lily
	lily.Eat2()
	fmt.Println("修改后lily1:",lily1)  //lily

}

2.类继承

1.类继承

package main

import "fmt"

type Human struct {
	//成员属性
	name string
	age int
	gender string
}

//在类外面绑定方法
func (this *Human) Eat(){
	fmt.Println(this.name + " is eating")
}

//定义一个学生类,去嵌套一个Hum
type student1 struct {
	hum Human //包含Human类型变量,此时是类的嵌套
	score float64
	school string
}

//定义一个老师,去继承Human
type Teacher struct {
	Human  //直接写human类型,没有字段名字
	subject string
}

func main(){
	s1 := student1{
		hum: Human{
			"lily",
			18,
			"女",
		},
		school:"双中",
	}

	fmt.Println("s1.name:",s1.hum.name)
	fmt.Println("s1.school:",s1.school)

	t1 := Teacher{}
	t1.subject = "语文" //下面这几个字段都是继承自Human
	t1.name = "王老师"
	t1.age = 23
	t1.gender = "女生"
	fmt.Println("t1:", t1)
	t1.Eat()
	
	//继承的时候,虽然没有定义字段名称,但是会自动创建一个默认的同名字段
	//这是为了在子类中依然可以操作父类,因为:子类父类可能出现同名的字段
	fmt.Println("t1.Human.name", t1.Human.name)
}

2.类访问权限-字段大小写-public-private

//在go语言中,权限都是通过首字母大小写来控制
//1.import ==》如果包名不同,那么只有大写字母开头的才是public的
//2.对于类里面的成员、方法 ==》只有大小写开头的才能在其他包中使用

类的继承.go

package src

import "fmt"

//在go语言中,权限都是通过首字母大小写来控制
//1.import ==》如果包名不同,那么只有大写字母开头的才是public的
//2.对于类里面的成员、方法 ==》只有大小写开头的才能在其他包中使用

type Human struct {
	//成员属性
	Name string
	Age int
	Gender string
}

//在类外面绑定方法
func (this *Human) Eat(){
	fmt.Println(this.Name + " is eating")
}

//定义一个学生类,去嵌套一个Hum
type Student1 struct {
	Hum Human //包含Human类型变量,此时是类的嵌套
	Score float64
	School string
}

//定义一个老师,去继承Human
type Teacher struct {
	Human  //直接写human类型,没有字段名字
	Subject string
}

//func main(){
//	s1 := student1{
//		hum: Human{
//			"lily",
//			18,
//			"女",
//		},
//		school:"双中",
//	}
//
//	fmt.Println("s1.name:",s1.hum.name)
//	fmt.Println("s1.school:",s1.school)
//
//	t1 := Teacher{}
//	t1.subject = "语文" //下面这几个字段都是继承自Human
//	t1.name = "王老师"
//	t1.age = 23
//	t1.gender = "女生"
//	fmt.Println("t1:", t1)
//	t1.Eat()
//
//	//继承的时候,虽然没有定义字段名称,但是会自动创建一个默认的同名字段
//	//这是为了在子类中依然可以操作父类,因为:子类父类可能出现同名的字段
//	fmt.Println("t1.Human.name", t1.Human.name)
//}

main.go

package main

import (
	"day02/08-类成员的访问权限/src"
	"fmt"
)

func main(){
	s1 := src.Student1{
		Hum: src.Human{
			"lily",
			18,
			"女",
		},
		School:"双中",
	}

	fmt.Println("s1.name:",s1.Hum.Name)
	fmt.Println("s1.school:",s1.School)

	t1 := src.Teacher{}
	t1.Subject = "语文" //下面这几个字段都是继承自Human
	t1.Name = "王老师"
	t1.Age = 23
	t1.Gender = "女生"
	fmt.Println("t1:", t1)
	t1.Eat()

	//继承的时候,虽然没有定义字段名称,但是会自动创建一个默认的同名字段
	//这是为了在子类中依然可以操作父类,因为:子类父类可能出现同名的字段
	fmt.Println("t1.Human.name", t1.Human.Name)
}

3.interface(接口)

package main

import "fmt"

//在go语言中,有专门的关键字,interface来代表接口
//interface不仅仅是用于处理多态的,它还可以接受任意的数据类型,类似viod
func main(){
	//func Println(a ...interface{}) (n int, err error) {
	fmt.Println()

	//var i, j, k int
	//定义三个接口
	var i, j, k interface{}
	names := []string{"duck", "lily"}
	i = names
	fmt.Println("i代表切片数组:",i)

	age := 20
	j = age
	fmt.Println("j代表数字:",j)

	str := "hello"
	k = str
	fmt.Println("k代表字符串:",k)

	//我们现在只知道k是interface,但是不能够明确知道它代表的数据类型
	kvalue, ok := k.(int)
	if !ok {
		fmt.Println("k不是int")
	}else{
		fmt.Println("k是int,值为:", kvalue)
	}

	//最常用的场景:把interface当成一个函数的参数, (类似于print),使用switch来判断用户输入的不同类型
	//根据不同类型。做相应的逻辑处理

	//创建一个具有三个接口类型的切片
	array := make([]interface{}, 3)
	array[0]=1
	array[1]="hello world"
	array[2]=3.14

	for _, value := range array{
		//可以获取当前接口的真正数据类型
		switch v := value.(type){
		case int:
			fmt.Printf("当前类型为int,内容为:%d\n",v)
		case string:
			fmt.Printf("当前类型为string,内容为:%s\n",v)
		case bool:
			fmt.Printf("当前类型为bool,内容为:%v\n", v) //%v可以自动推导输出类型
		default:
			fmt.Printf("不是合理的数据类型")
		}
	}
}

4.多态

1.定义一个接口,里面设计好需要的接口,可以有多个

2.任何实现了这个接口的类型,都可以赋值给这个接口,从而实现多态

3.多个类之间不需要有继承关系

4.如果interface中定义了多个接口,那么实际的类必须全部实现接口函数,才可以赋值

package main

import "fmt"

//实现go多态,需要定义接口
//人类的武器发起攻击,不同的等级子弹效果不同

//定义一个接口,注意类型是interface
type IAttack interface {
	//接口函数可以有多个,但是只能有函数原型,不可用有实现
	Attack()
}

//低等级
type HumanLowLevel struct {
	name string
	level int
}

//给结构绑定一个方法
func (a *HumanLowLevel) Attack(){
	fmt.Println("我是:", a.name,"等级为:",a.level,"造成1000伤害")
}


//高等级
type HumanHighLevel struct {
	name string
	level int
}

//给结构绑定一个方法
func (a *HumanHighLevel) Attack(){
	fmt.Println("我是:", a.name,"等级为:",a.level, "造成5000伤害")
}


//定义一个多态的通用接口,传入不同的对象,调用同样的方法,实现不同的效果,===》多态
func DoAtack(a IAttack){
	a.Attack()
}

func main(){
	var player IAttack //定义一个包含Attack的接口变量

	lowLevel1 := HumanLowLevel{
		"David",
		1,
	}

	HighLevel := HumanHighLevel{
		"David",
		10,
	}

	lowLevel1.Attack()
	HighLevel.Attack()

	player = &lowLevel1  	//对player赋值为lowlevel,接口需要使用指针类型来赋值
	player.Attack()

	player = &HighLevel
	player.Attack()

	fmt.Println("多态。。。")
	DoAtack(&lowLevel1)
	DoAtack(&HighLevel)
}

四、并发相关

1.基础

1.并发和并行

并发:电脑同时听歌、看小说、看电影。cpu根据时间片进行划分,交替执行这三个程序。我们人可以感觉是同步的

并行:多个cpu(多核)同时执行

2.go程(goroutine)

c语言里实现并发的过程是使用多线程(C++的最小资源单元),进程

go语言里不是叫线程,而是go程===》goroutine,go程是go语言原生支持的,每个go程占用的系统资源远远小于线程,一个go程大约需要4K-5K的内存资源,一个程序可以启动大量的go程:

1).线程==》几十个

2).go程可以启动成百上千个, ==》对于高并发,性能非常好

3).只需要在目标函数前加上go关键字即可

package main

import (
	"fmt"
	"time"
)

//这个将用于子go程使用
func display(){
	count := 1
	for {
		fmt.Println("========》这是子go程:", count)
		count++
		time.Sleep(1 * time.Second)
	}
}

func main(){
	//启动子go程
	//go display()
	go func() {
		count := 1
		for {
			fmt.Println("========》这是子go程:", count)
			count++
			time.Sleep(1 * time.Second)
		}
	}()

	//主go程
	count := 1
	for {
		fmt.Println("这是主go程:",count)
		count++
		time.Sleep(1 * time.Second)
	}
}
//启动多个子go程,他们会竞争cpu资源

package main

import (
	"fmt"
	"time"
)

//这个将用于子go程使用
func display(num int){
	count := 1
	for {
		fmt.Println("========》这是子go程:",num,"当前count值:", count)
		count++
		//time.Sleep(1 * time.Second)
	}
}

func main(){
	//启动子go程
	go display()
	for i := 0; i < 3; i++ {
		go display(i)
	}

	//go func() {
	//	count := 1
	//	for {
	//		fmt.Println("========》这是子go程:", count)
	//		count++
	//		time.Sleep(1 * time.Second)
	//	}
	//}()

	//主go程
	count := 1
	for {
		fmt.Println("这是主go程:",count)
		count++
		time.Sleep(1 * time.Second)
	}
}

2.提前退出go程

package main

import (
	"fmt"
	"runtime"
	"time"
)

//GOEXIT ===> 提前退出当前go程
//return ===> 返回当前函数
//exit ===> 退出当前进程

func main(){
	go func(){
		func(){
			fmt.Println("这是子go程内部的函数!")
			//return //返回当前函数。结果==》
			// 这是主go程
			//这是子go程内部的函数!
			//子go程结束!
			//over!

			//os.Exit(-1) //退出进程。结果==>
			//这是主go程
			//这是子go程内部的函数!
			//exit status 0xffffffff

			runtime.Goexit() //退出当前go程
			//这是主go程
			//这是子go程内部的函数!
			//over!

		}()

		fmt.Println("子go程结束!")
	}()

	fmt.Println("这是主go程")
	time.Sleep(5 * time.Second)
	fmt.Println("over!")
}

3.1无缓冲管道channel 

package main

import (
	"fmt"
	"time"
)

func main() {
	//当涉及到多go程时,c语言使用互斥量,上锁来保持资源同步,避免资源竞争问题
	//go语言也支持这种方式,但是go语言使用管道,通道,channl更好解决
	//使用channel不用去加解锁
	//A 往通道里写数据,B从管道里读数据,go自动帮我们做好了数据同步

	//创建管道:创建一个装数字的管道
	//strChan := make(string int)

	numChan := make(chan int, 5) //同map一样,使用make,否则就是nil
	//不创建空间就是无缓冲channel,创建就是有缓冲的管道
	//numChan := make(chan int 10)

	//创建两个go程,父亲写数据,儿子读数据
	go func(){
		for i := 0; i <= 50; i++{
			//读数据
			data := <- numChan
			fmt.Println("子go程1,读取数据,data:",data)
		}
	}()

	go func(){
		for i := 0; i <= 20; i++{
			//向管道中写入数据
			numChan <- i
			fmt.Println("子go程2,写入数据:",i)
		}
	}()

	for i := 20; i <= 50 ; i++ {
		//向管道中写数据
		numChan <- i
		fmt.Println("===>这是主go程,写入数据:", i)
	}

	time.Sleep(5*time.Second)
}

3.2有缓冲信道

package main

import (
	"fmt"
	"time"
)

func main(){
	//numChan := make(chan int, 10)
	//1.当缓冲写满的时候,写阻塞,当被读取后,再恢复写入
	//2.当缓冲区读取完毕,读阻塞
	//3.如果缓冲没有使用ake分配空间,那么管道默认是nil的,读取,写入都会阻塞
	//4.对于一个管道,读与写的次数,必须对等

	var names chan string //默认是nil

	names = make(chan string, 10)

	go func(){
		fmt.Println("name:", <-names )
	}()

	names <- "hello" //由于names是nil的,写操作都会阻塞在这里
	time.Sleep(1*time.Second)

	numsChan1 := make(chan int, 10)
	//写
	go func(){
		for i:=0; i<50; i++{
			numsChan1<-i
			fmt.Println("写入数据:", i)
		}
	}()

	//读,当主程序被管道阻塞时,那么程序将被锁死崩溃
	//要救我们一定要读写次数一致
	go func(){
		for i:=0; i<50; i++{
			numsChan1<-i
			fmt.Println("写入数据:",i)
		}
	}()

	for {
		fmt.Println("这是主go程,正在死循环")
		time.Sleep(1*time.Second)
	}
}

//1.当管道的读写次数不一致的时候
//	1.如果组阻塞主go程,那么程序会崩溃
//	2.如果阻塞在子go程,那么会出现内存泄漏

4.for range遍历

5.管道总结

6.多go程读写

7.判断管道是否已经关闭

8.单向通道

五、函数

1.头等函数

​​​​​​​支持头等头等函数的编程语言:

1.可以把函数赋值给变量,

2.可以把函数作为其他函数的参数

3.可以把函数作为其他函数的返回值。

2.匿名函数

//调用匿名函数的方法:可以不用赋值给变量.直接在定义之后使用(),立即调用该函数

//匿名函数
func main(){
	a := func(){ //将匿名函数赋值给变量a.
		fmt.Println("hello world first class function")
	}
	a() //调用该匿名函数的唯一方法是使用变量a
	fmt.Printf("%T", a)
}


//调用匿名函数的方法:可以不用赋值给变量.直接在定义之后使用(),立即调用该函数
func main(){
	func(){
		fmt.Println("hello world first class function")
	}()	//直接在定义之后使用(),立即调用该函数
}

//向匿名函数传参
func main() {
	func(n string){
		fmt.Println("welcom", n)
	}("Gophers")
}

3.用户自定义函数

//用户自定义的函数类型
type add func(a int, b int) int

func main() {
	var a add = func(a int, b int) int {
		return a + b
	}
	s := a(5, 6)
	fmt.Println("Sum", s)
}

4.高阶函数

高阶函数(满足下面条件之一的函数)
接收一个或多个函数作为参数
返回值是一个函数

4.1把函数作为参数,传递给其他函数

//把函数作为参数,传递给其他函数
func simple(a func(a, b int) int) {
	fmt.Println(a(60, 70))
}

func main() {
	f := func(a, b int) int {
		return a + b
	}
	simple(f)
}

4.2在其他函数中返回函数

//在其他函数中返回函数
func simple() func(a,b int) int {
	f := func(a, b int) int{
		return a + b
	}
	return f
}

func main(){
	s := simple()
	fmt.Println(s(600,7))
}

5.闭包

//闭包
//闭包是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数外部时,就称这样的匿名函数为闭包
func main(){
	a := 5
	func(){
		fmt.Println("a=", a)
	}()
}

//每一个闭包都会绑定一个他自己的外围变量
func appendStr() func(string) string{ //返回一个闭包。这个闭包绑定了变量t.
	t := "hello"
	c := func(b string) string{
		t = t+ " " +b
		return t
	}
	return c
}

func main(){
	//a, b 都是闭包,绑定了各自的t值
	a := appendStr()
	b := appendStr()
	fmt.Println(a("World"))
	fmt.Println(b("Everyone"))

	fmt.Println(a("Gopher"))
	fmt.Println(b("!"))
}

6.头等函数的实际用途

//头等函数的实际用途
//eg:创建一个程序,基于一些条件,来过滤一个student切片。

//定义一个student类型
type student struct{
	firstName string
	lastName string
	grade string
	country string
}

//编写filter函数,接收一个student切片和一个函数作为参数。这个函数用于计算一个学生是否满足筛选条件。
func filter(s []student ,f func(student) bool) []student{
	var r []student
	for _, v := range s {  //遍历student切片,将每个学生作为参数传递给函数f
		if f(v) == true{
			r = append(r,v)
		}
	}
	return r
}

func main(){
	s1 := student{
		firstName: "chen",
		lastName:  "ran",
		grade:     "B",
		country:   "china",
	}

	s2 := student{
		firstName: "li",
		lastName:  "yanfang",
		grade:     "A",
		country:   "UK",
	}
	s := []student{s1,s2}
	f := filter(s, func(s student)bool {
		if s.grade == "B"{
			return true
		}
		return false
	})
	fmt.Println(f)

	c := filter(s, func(s student) bool{
		if s.country == "UK"{
			return true
		}
		return false
	})
	fmt.Println(c)
}


//练习:编写一个程序,这个程序会对切片的每个元素执行相同操作,并返回结果。
//例如,希望将切片中的所有整数乘以5,并返回结果
func iMap(s []int, f func(int) int) []int {
	var r []int
	for _, v := range s {
		r = append(r, f(v))
	}
	return r
}

func main(){
	a := []int{5,6,7,8,9}
	r := iMap(a,func(n int) int{
		return n * 5
	})
	fmt.Println(r)

}


版权声明:本文为weixin_44825767原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。