一、基础语法
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)
}







