Go的语言基本教程
一.Go的语言结构
1.Go示例
基础组成部分:包声明,引入包,函数,变量,语句&表达式,注释
package main
import "fmt"
func main(){ //注意,函数的第一个“{”不能单独一行
fmt.Println("Hello,Lry")
}
1.package main 是包声明,定义了包名,(包名和文件名没有关系),package 后面的包可以是自己自定义的名称,然后再从main.go文件中进行import,类似于C语言的头文件.h。但是如果是一个可独立执行的程序,则必须声明package main包 同一个文件夹下的文件只能有一个包名,否则编译报错。
2.import “fmt” 告诉我们需要引入fmt包,要使用fmt里面的函数,fmt包里面包含了格式化I/O的函数
3.func main()是程序开始执行的函数,每个可独立执行的程序必须有一个main函数,func是关键字,用来声明函数。
4.fmt.Println(“hello,Lry”)使用fmt中的Println函数进行输出,会在最后自动增加换行符\n,当然使用fmt.Print(“Hello,Lry\n”)也可以实现换行。go中不支持 char := 79 不像C语言一样 ,fmt.Println和 fmt.Print可以直接输出多个变量,并在变量之间自动添加空格,但不支持格式化字符串。
是否自动换行 | 是否在参数之间自动空格 | 是否支持格式化 | |
---|---|---|---|
fmt.Println() | 是 | 是 | 不是 |
fmt.Print() | 不是 | 不是 | 不是 |
fmt.Printf() | 不是 | 不是 | %d,%c… |
5.当标识符(常量 ,变量,函数名,类型,结构字段)以一个大写字母开头时,那么这种对象就可以被外部包的代码所引用(前提是需要先导入这个包),类似于C++中的public,如果以一个小写字母开头,则对包外是不可见的,但是在他们整个包的内部是可见的并且可用的,类似于C++中的protected
2.Go执行
文件名为hello.go 在终端输入 go run hello.go则可以直接运行,或者是 go build hello.go 会生成一个hello二进制文件,直接在终端./hello即可运行
二:Go 基础语法
1.字符串连接
package main
import "fmt"
func main(){
fmt.Println("Liu"+"Ruiyang")
}
2.关键字
3.格式化字符串
package main
import "fmt"
func main() {
var a = 1
var string1 = "Hello, world!"
var b = "int=%d, string=%s\n"
var c = fmt.Sprintf(b, a, string1)
fmt.Printf(b, a, string1) // <==> fmt.Printf("int=%d,string=%s\n", a, string1)
fmt.Println(c)
}
fmt.Sprintf()根据格式化参数生成格式化字符串并返回该字符串
fmt.Printf()根据格式化参数生成格式化字符串并写入标准输出
三:Go语言数据类型
布尔型,数字类型,字符串类型,派生类型(指针,数组,结构化(struct),Channel类型,函数类型,切片类型,接口类型(interface),Map类型)
数字类型
uint8 uint16 uint32 uint64 无符号整数8/16/32/64位 int8 int16 int32 int64 有符号整数8/16/32/64
浮点数 float32 float64 复数 complex32 complex64 其他数字类型 rune 类似uint32 byte 类似 uint8 uintptr 无符号整型,存放一个指针
package main 无需定义int float 系统会自动识别
import "fmt"
func main() {
var a = 1.5
var b = 2
fmt.Println(int(a) + b)
fmt.Println(a + float64(b))
}
四:Go语言变量
声明变量 var identifier type 例:var a int 可以一次声明多个变量
package main
import "fmt"
func main() {
var a string = "Runoob"
fmt.Println(a)
var b, c int = 1, 2
fmt.Println(b, c)
}
1.变量声明
第一种,指定变量类型,如果没有初始化,默认为0 bool值默认为false
第二种,根据值自动判断变量类型 var a = 2.4 就相当于 var a float64 = 2.4
第三种,如果变量已经使用 var 声明过了,再使用 *:=* 声明变量,就产生编译错误
如果你声明了一个局部变量却没有在相同的代码块中使用它,同样会得到编译错误,但是全局变量可以声明但不使用
**交换值:**如果你想要交换两个变量的值,则可以简单地使用 a, b = b, a,两个变量的类型必须是相同。
例如 var a int = 2 a := 3 就会产生编译错误 **:=**相当于声明变量并复制(但是这种命名只可以用于函数体内,不可以声明全局变量)
a := 2
等同于
var a int = 2
2.多变量声明
package main
import "fmt"
var x, y int
var ( // 这种因式分解关键字的写法一般用于声明全局变量
a int
b bool
)
var c, d int = 1, 2
var e, f = 123, "hello"
//这种不带声明格式的只能在函数体中出现
//g, h := 123, "hello"
func main(){
g, h := 123, "hello"
fmt.Println(x, y, a, b, c, d, e, f, g, h)
}
五:Go语言常量
1.const
const identifier [type] = value // type可以省略
package main
import "unsafe"
const (
a = "abc"
b = len(a)
c = unsafe.Sizeof(a) //const 还以用于枚举
)
func main(){
println(a, b, c) // c = 16 因为在go语言中 字符串是一个结构体 包括一个指向字符串数据的指针 8个字节
//还有一个表示字符串长度的整数 8个字节 所以sizeof(string) 为16个字节
}
2.iota
可以认为是一个可以被编译器修改的常量
iota在const出现时将第一行iota重置为0,const中没新增一行iota就会加1,所以也可以把iota认为是const枚举内部的行索引index
iota 只是在同一个 const 常量组内递增,每当有新的 const 关键字时,iota 计数会重新开始。
package main
import "fmt"
func main(){
const (
a = iota //iota = 0
b //iota = 1
c //iota = 2
d = "ha" //iota = 3
e //iota = 4
f = 100 //iota = 5
g //iota = 6
h = iota //iota = 7
i //iota = 8
)
fmt.Println(a,b,c,d,e,f,g,h,i)
}
输出:0 1 2 ha ha 100 100 7 8 //在const()中b c e g i 都省略了初始化,则会按照上面的数据进行初始化
六:Go语言运算符
1.算术运算符
2.关系运算符
3.逻辑运算符
4.位运算符
5.赋值运算符
6.其他运算符
&a 给出变量a的实际地址 *a 一个指针变量
七:Go语言条件语句
1.if语句 if…else… if嵌套
if语句注意事项
1.条件判断不需要用括号括起来
2.可以在条件判断语句之前进行变量声明初始化用;与条件判断语句分隔开
3.{}必须存在,哪怕只有一行
4.第一个"{"必须和if在一行
5.在有返回值函数中,return不能在if语句里
package main
import "fmt"
func main(){
if num := 9; num < 0 {
fmt.Println(num,"is negative")
} else if num < 10{
fmt.Println(num,"has 1 digit")
} else {
fmt.Println(num,"has multiple digits")
}
}
2.switch语句 Type Switch 语句 fallthrough用法
switch var {
case value1:
case value2:
...
default:
...
}//var可以是任意是任意类型 ,value1,value2可以是同类型的任意值,类型不仅是常量,整数等等,但是要是同类型,也可以是结果为同类型的表达式
type Switch语句
switch x.(type){
case type:
statement(s);
case type:
statement(s);
/* 你可以定义任意个数的case */
default: /* 可选 */
statement(s);
}
fallthrough 不会判断下一条case语句表达式结果是否为True,会强制执行后面的case语句
package main
import "fmt"
func main() {
switch {
case false:
fmt.Println("1、case 条件语句为 false")
fallthrough
case true:
fmt.Println("2、case 条件语句为 true")
fallthrough
case false:
fmt.Println("3、case 条件语句为 false")
fallthrough
case true:
fmt.Println("4、case 条件语句为 true")
case false:
fmt.Println("5、case 条件语句为 false")
fallthrough
default:
fmt.Println("6、默认 case")
}
}
会输出
2、case 条件语句为 true
3、case 条件语句为 false
4、case 条件语句为 true
执行顺序从第一个表达式为true开始执行完后有fallthrough不会判断下一条case语句是否为真,直接执行下一条语句
3.select语句
select只能用于通道操作,每隔case必须是一个通道操作,要么是发送要么是接受,select会监听所有通道,一旦有一个准备好,则会执行相应case中的代码块,如果多个准备好,则会随机选择一个通道进行执行,如果都没准备好,则执行default的代码块,如果没有default语句的话,select将会阻塞,知道某个通道可以运行
package main
import(
"fmt"
"time"
)
func main(){
c1 := make(chan string) //创建通道 make(chan string) 创建一个传递字符串数据类型的无缓冲通道
c2 := make(chan string)
go func(){
time.Sleep(1 * time.Second)
c1 <- "one"
}()
go func(){
time.Sleep(2 * time.Second)
c2 <- "two"
}() //func(){...}创建一个匿名函数 func(){...}()创建一个匿名函数并随之立即调用
//go func(){...}()使用go关键字启动一个新的goroutine执行匿名函数,goroutine是相当于线程,用来并发执行,在这里就是起到了并发的让通道等待数据
for i := 0; i < 2; i++ {
select {
case msg1 := <-c1:
fmt.Println("received", msg1)
case msg2 := <-c2:
fmt.Println("received", msg2) //具体执行哪一个取决于goroutine的调度,如果都是等待一秒的话顺序就随机了,但是现在是c1等待一秒,c2等待两秒,一般就是c1通道先接受数据
}
}
}
如果要不断地接受数据
package main
import "fmt"
func main() {
// 定义两个通道
ch1 := make(chan string)
ch2 := make(chan string)
// 启动两个 goroutine,分别从两个通道中获取数据
go func() {
for {
ch1 <- "from 1"
}
}()
go func() {
for {
ch2 <- "from 2"
}
}()
// 使用 select 语句非阻塞地从两个通道中获取数据
outerLoop:
for {
select {
case msg1 := <-ch1:
fmt.Println(msg1)
case msg2 := <-ch2:
fmt.Println(msg2)
default:
// 如果两个通道都没有可用的数据,则执行这里的语句
fmt.Println("no message received")
break outerLoop
}
}
}
八:Go语言循环语句
1.for循环+循环嵌套
for 初始化;判断条件;赋值表达式{ //for {...} 无限循环 相当于C中的for(;;)
...
}
基本循环同c语言
for-each range循环
for key, value := range oldMap { //注意是逗号
newMap[ket] = value //key 和 value 可以省略 for _,value := range oldMap for key := range oldMap
}
package main
import "fmt"
func main() {
strs := []string{"lry", "zxy"}
for i, s := range strs {
fmt.Println(i, s)
}
numbers := [5]int{1, 2, 3, 4, 5}
for i, n := range numbers {
fmt.Println(i, n)
}
for _, n := range numbers {
fmt.Println(n)
} //读取value值
for n := range numbers {
fmt.Println(n)
} //读取key值
}
//取2到100之间的素数
package main
import "fmt"
var j int
func main() {
for i := 2; i < 100; i++ {
for j = 2; j < (i / j); j++ {
if i%j == 0 {
break
}
}
if j > (i / j) {
fmt.Println(i)
}
}
}
//99乘法表
package main
import "fmt"
var j int
func main() {
for i := 1; i < 10; i++ {
for j = 1; j <= i; j++ {
fmt.Print(j, "*", i, "=", i*j, " ")
}
fmt.Println()
}
}
2.break语句
break在多重循环中如果在内层循环使用break只会终止内层循环,不会使整个循环终止,如果想要终止整个循环可以采用break Lable的形式
例如:
package main
import "fmt"
func main(){
re:
for i := 1 ; i < 10 ; i++{
for j :=1 ; j < 10 ; j++{
if j%2 == 0{
break re
}
}
}
}
break在switch语句中直接终止跳出switch语句,在select语句中不能直接跳出,如果要提前终止select语句则需要使用return提前结束select语句
package main
import "fmt"
var i int = 0
var j int = 0
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
for {
i++
a := i
ch1 <- a
}
}()
go func() {
for {
j++
a := j
ch2 <- a
}
}()
for {
select {
case a := <-ch1:
fmt.Println("ch1:", a)
case b := <-ch2:
fmt.Println("ch2:", b)
if b > 5 {
return
}
}
}
}
3.continue语句
continue也可以使用标志 continue Lable
九:Go语言函数
1.函数定义+函数调用
func function_name([parameter list])[return_type]
1.func声明
2.function_name函数名字
3.[parameter list]参数列表,可有可无 参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
4.[return_type]返回值类型,可有可无
func add(num1,num2 int) int {
return num1+num2
}
package main
import "fmt"
func add(num1, num2 int) int { //声明函数
return num1 + num2
}
func main() {
fmt.Println(add(1, 2)) //调用函数
}
函数可以返回多个值
func swap(x,y string) (string,string){
return y,x //值传递
}
func swap(x *int ,y *int){ //引用传递
var temp int
temp = *x
*x = *y
*y = temp
}
2.函数传递+闭包+方法
//函数作为实参
import (
"fmt"
"math"
)
func main(){
getSquare := func(x float64) float64{
return math.Sqrt(x)
} //声明函数变量
fmt.Println(getSquare(9)) //使用函数
}
//函数闭包(匿名函数)
匿名函数作为闭包,在函数内部定义的函数,优越性在于可以直接使用函数内的变量,不用再声明,
func getSequence()func ()int {
i := 0
return func() int{
i++
return i
}
}
func main(){
nextNumber := getSequence()
fmt.Println(nextNumber())
fmt.Println(nextNumber())
fmt.Println(nextNumber())
}
//方法:一个方法就是一个接收者的函数 例如可以直接把一个方法给结构体,那么就可以通过结构体调用这个函数
func (varible_name varible_type) function_name() [return_type]{
...
}
type circle struct{
radius float64
}
func (c circle) area() float64{
return 3.14*c.radius*c.radius
}
func main(){
var c1 circle
c1.radius = 10.00
fmt.Println("The area is:",c1.area())
}
十:Go语言数组
1.声明数组及初始化+访问数组元素
var arrayName [size]array_type //var a [10]int <==> a := [5]int
数组在声明如果没有进行初始化,那么就会默认初始化为0
如果数组长度不固定则可以采用[...]用"..."来代替长度,编译器会根据元素个数来确定数组大小
还可以通过指定下标进行初始化
a := [5]int{1:1,2:3} // <==> a := [5]int{0, 1 , 3 , 0 , 0}
利用下标索引进行访问数组
2.多维数组
var name [size1][size2][size3].... type // var a [2][3][4]int int类型的三维数组
3.向函数传递数组 (区分数组和切片!)
1.传递数组
func average(array [5]int,size int) float32 { //数组作为形参时必须规定大小 也就是说如果形参未定义数组长度,那么传递过来的数组也不能初始化数组长度,这时候就不是数组了,叫切片 如果形参规定了数组长度,则传递过来的数组也必须初始化数组长度,简而言之,形参和调用函数传递的参数要一致
sum := 0
var avg float32
for i := 0 ; i < size ; i++ {
sum += array[i]
}
avg = float32(sum)/float32(size)
return avg
}
func main(){
b := [5]int{1,2,3,4,5}
fmt.Println(average(b,5))
}
2.传递指针 //如果想要在函数内修改数组,则需要通过传递数组指针实现
func pointer(array *[5]int){
for i := 0; i < len(*array) ; i++{
(*array)[i] = (*array)[i] * 2
}
}
func main(){
arr := [5]int{1,2,3,4,5}
pointer(&arr)
} //经过调用arr的值都会改变
十一:Go语言指针
1.指针定义
var var_name *var_type //var ip *int
2.指针使用
package main
import "fmt"
func main(){
a := 10
var ip *int
ip = &a
fmt.Printf("a的地址是:%x\n",&a)
fmt.Printf("ip变量存储的指针地址是:%x\n",ip)
fmt.Printf("*ip变量的值是:%d\n",*ip)
}
3.空指针 nil
var ip *int
fmt.Printf("ip变量的值:%x\n",ip)
//空指针判断
ip == nil //ip != nil
4.指针数组
package main
import "fmt"
const MAX int =3
func main(){
a := []int{10 ,100 ,200}
var p [MAX]*int
for i:=0 ; i < MAX ; i++ {
p[i] = &a[i]
}
for i:=0 ; i < MAX ; i++ {
fmt.Printf("a[%d] = %d\n",i,*p[i])
}
}
5.指向指针的指针
var ptr **int
package main
import "fmt"
func main(){
a := 10
var p1 *int
var p2 **int
p1 = &a
p2 = &p1
fmt.Println(a , *p1 , **p2)
}
6.Go语言指针作为函数参数
package main
import "fmt"
func swap(x *int,y *int){
*x,*y = *y,*x
}
func main(){
a := 10
b := 100
fmt.Println(a,b)
var p1 *int
var p2 *int
p1 = &a
p2 = &b
swap(p1,p2)
fmt.Println(a,b)
}
十二:Go语言结构体
1.定义结构体
type struct_name struct{
....
}
variable_name := struct_name{value1,value2,value3...} // variable_name := struct_name{key1:value1,key2:value2,key3:value3....}
type books struct{
title string
author string
subject string
book_id int
}
fmt.Println(books{"Go","Lry","Math",020719})
fmt.Println(title:"Go",author:"Lry",subject:"Math",book_id:020719) //忽略的字段为0或空
访问结构体同C语言 books.title ="Go"
2.结构体作为函数的参数
type books struct{
title string
author string
subject string
book_id int
}
func print(Book books){
fmt.Printf("the title is %s\n",Book.title)
....
}
3.结构体指针
type books struct{
title string
author string
subject string
book_id int
}
func main(){
var book1 books
var book2 books
//进行初始化....
print(&book1)
print(&book2)
}
func print(book *books){
fmt.Printf("the title is %s",book.title)
}
十三:Go语言切片
切片实际上就是对数组的抽象,当数组大小不知道是,我们可以声明一个切片(“动态数组”)
1.切片定义
var name []type //容量为0
var slice1 []type = make([]type, len) // slice1 := make([]type, len, capacity) capacity是可选参数 len必须有
struct Slice
{
byte* array; // actual data
uintgo len; // number of elements
uintgo cap; // allocated number of elements
}
2.切片初始化
s := []int{1,2,3} //直接初始化
s := arr[start:end]
/*
当start和end都为空时 arr[:] 这是对数组arr的引用
当arr[:end]时, 从第一个元素一直到arr的end-1元素
当arr[start:]时,从start个元素一直到arr最后一个元素
当arr[start:end]时,从start元素到arr的end-1个元素
s1 := s[start:end]通过切片s初始化切片s1
s := make([]type, len, cap) 通过make初始化切片s
*/
3.len() cap()
len()函数获取切片的长度 cap()函数测量切片的容量(最长可以达到多少)
package main
import "fmt"
func main(){
s := make([]int, 3, 5)
printSlice(s)
}
func printSlice(num []int){
fmt.Println("len=","cap=","slice=",len(num),cap(num),num)
}
4.空切片nil
var s []int
if s == nil{
fmt.Println("the slice is empty")
}
5.append() 和 copy()
如果想增加切片容量的话需要先创建一个更大的新的切片并把原有的切片copy过来
package main
import "fmt"
func main(){
var s1 []int
printSlice(s1)
s1 = append(s1,0)
printSlice(s1)
s1 = append(s1,1)
printSlice(s1)
s1 = append(s1,2,3,4)
printSlice(s1)
s2 := make([]int ,len(s1),(cap(s1))*2)
copy(s2,s1)
printSlice(s2)
}
func printSlice(x []int){
fmt.Println("len=","cap=","slice=",len(x),cap(x),x)
}
//当append(list,[params])时,先判断cap(list)是否大于等于len(list)+len([params]),如果大于,则cap不变,否则,cap=2*max{(len(list)+len([params]))}, 当需要的容量超过原切片容量的两倍时,会使用需要的容量作为新容量。
//当原切片长度小于1024时,新切片的容量会直接翻倍。而当原切片的容量大于等于1024时,会反复地增加25%,直到新容量超过所需要的容量。
十四:Go语言范围(Range)
1.数组和切片 遍历简单的切片,2**%d 的结果为 2 对应的次方数:
package main
import "fmt"
func main(){
s :=[]int{1,2,4,8,16,32,64,128}
for key,value := range s{
fmt.Println("2**",key,"=",value)
}
}
2.字符串
package main
import "fmt"
func main(){
for i,v := range "hello"{
fmt.Printf("index:%d\tchar:%c\n",i,v)
}
}
3.映射(Map)
package main
import "fmt"
func main(){
map1 := make(map[int]float32)
map1[1] = 1.0
map1[2] = 2.0
map1[3] = 3.0
map1[4] = 4.0
for key,value := range map1{
fmt.Printf("map1[%d]:%f\n",key,value)
}
for _,value :=range map1{
fmt.Printf("value:%f\t",value)
}
for key :=range map1{
fmt.Printf("key:%d\t",key)
}
}
//map 是一种无序的数据结构,这意味着键值对的存储顺序是不确定的。每次遍历 map 时,键的顺序可能会有所不同。这是因为 map 的底层实现并不保证元素的顺序。
4.通道
package main
import "fmt"
func main(){
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
for v := range ch{
fmt.Println(v)
}
}
十五:Map
1.定义map
Map是一种无序的数据结构,最重要的是可以通过key来检索数据,Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。
map1 := make(map[keyType]ValueType,initialCapacity) //map1 := make(map[int]string) initialCapacity可选参数,表示容量,当 Map 中的键值对数量达到容量时,Map 会自动扩容
2.map操作
//创建map
m := make(map[string]int ,10)
m ={
"apple":1,
"banana":2,
"orange":3
}
//获取元素
v1 := m["apple"]
v2,ok := m["pear"] //如果没有pear键,则ok为false
//修改元素
m["apple"] = 5
//获取map长度
len := len(m)
//遍历Map
for key,value := range m{
fmt.Printf("key=%s, value=%d\n", key, value)
}
//删除元素
delete(m,"banana")
十六:Go语言类型转换 不支持隐式类型转换
1.字符串类型
str := "10"
var num int
num,err = strconv.Atoi(str) //strconv.Atoi返回两个值,第一个是转换完后的整数值,第二个是可能发生错误
package main
import (
"fmt"
"strconv"
)
func main(){
str := "123"
var num int
var err error
num,err = strconv.Atoi(str)
if err!= nil{
fmt.Println("error")
}else{
fmt.Printf("%s->%d",str,num)
}
}
2.接口类型
两种情况:类型断言,类型转换
类型断言:用于将接口类型转换为指定的类型
value.(type) / value.(T)
value是接口类型的变量,type/T是要转换的类型,如果断言成功,返回转换后的值和一个布尔值
package main
import "fmt"
func main(){
var i interface{} = "Lry"
str , ok := i.(string)
if ok{
fmt.Printf("%s is a string\n",str)
}else{
fmt.Println("Failed")
}
}
类型转换:将一个接口类型的值转为另一个接口类型
T(value) T是目标接口类型,value是要转换的值 必须保证目标接口类型和要转换的值兼容
package main
import "fmt"
type Writer interface{
Write([]byte)(int , error)
}
type StringWriter struct{
str string
}
func (sw *StringWriter) Write (data []byte)(int,error){
sw.str += string(data)
return len(data),nil
}
func main(){
var w Writer = &StringWriter{}
sw := w.(*stringWriter) //将writer接口类型转换为stringWriter接口类型
sw.str = "lry"
fmt.Println(sw.str)
}
十七:Go语言接口
1.接口定义和实现
interface是Go语言一种类型,通过描述类型必须实现的方法,规定了类型的行为契约,接口可以让我们将不同的类型绑定到一组公共的方法上
接口特点:
1.隐式实现:没有关键字显式声明某个类型实现了某个接口,只要一个类型实现了接口要求的所有方法,该类型就自动被认为实现了该接口
2.接口类型变量:存储实现该接口的任意值,包括两个部分:动态类型(存储实际的值类型),动态值(存储具体的值)
3.零值接口:接口的零值是nil,一个未初始化的接口变量值为nil,不包含任何动态类型或动态值
4.空接口:interface{},可以表示任何类型
接口常见用法:
1.多态:不同类型实现同一接口,实现多态行为
2.解耦:通过接口定义依赖关系,降低模块之间的耦合
3.泛化:使用空接口表示任意类型
type interface_name interface{
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
method_name4 [return_type]
...
}
type struct_name struct{
variables
}
func(struct_name_variable struct_name) method_name1() [return_type]{
方法实现
}
func(struct_name_variable struct_name) method_namen() [return_type]{
方法实现
}
例子:
type Shape interface{
Area() float64
Perimeter() float64
}//Shape是一个接口,实现了Area和Perimeter方法,任意类型只要实现了这两个方法,就被认为实现了Shape接口
//实现接口
package main
import (
"fmt"
"math"
)
type Shape interface{
Area() float64
Perimeter float64
}
type Circle struct{
Radius float64
}
// circle实现Shape接口
func(c Circle) Area() float64{
return math.Pi * c.Radius * c.Radius
}
func(c Circle) Perimeter() float64{
return 2 * math.Pi * c.Radius
}
func main(){
c := Circle{Radius:5}
var s Shape = c //接口变量存储实现了接口的类型
fmt.Println("Area",s.Area())
fmt.Println("Perimeter:",s.Permimeter())
}
2.空接口
任意类型都实现了空接口,常用于存储任意类型数据的场景,如泛型容器,通用参数等
package main
import "fmt"
func printValue(val interface{}){
fmt.Printf("value:%v,Type:%T",val,val)
}
func main(){
printValue(42) //int
printValue("321") //string
printValue([]int{2,3}) //slice
}
3.类型断言
package main
import "fmt"
func main(){
var i interface{} = "lry"
str := i.(string) //value := iface.(Type) iface是接口变量,Type是要断言的具体类型,类型不匹配触发panic
}
func main(){
var i interface{} = "lry"
str , ok := i.(string)
if ok {
fmt.Printf("String:%s",str)
}else {
fmt.Println("Failed")
}
}
4.类型选择
package main
import "fmt"
func printType(val interface{}) {
switch v := val.(type) {
case int:
fmt.Println("Integer:", v)
case string:
fmt.Println("String:", v)
case float64:
fmt.Println("Float:", v)
default:
fmt.Println("Unknown type")
}
}
func main() {
printType(42)
printType("hello")
printType(3.14)
printType([]int{1, 2, 3})
}
5.接口组合
package main
import "fmt"
type Writer interface{
write(data string)
}
type Reader interface{
Read() string
}
type RW interface{
Reader
Writer
}
type file struct{}
func (f file) Read() string{
return "Reading data"
}
func (f file) write(data string){
fmt.Println("Writing data",data)
}
func main(){
var rw Rw = file{}
fmt.Println(rw.Read())
rw.write("Lry")
}
十八:Go语言错误处理
1.error接口
Go标准库提供了error接口
type error interface{
Error() string //任何实现了 Error() 方法的类型都可以作为错误。
}
//使用errors包创建错误
import(
"errors"
"fmt"
)
func main(){
err := error.New("this is an error")
fmt.Println(err)
}
//函数通常在最后的返回值中返回错误信息,使用 errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
//自定义错误
package main
import (
"fmt"
)
type DivideError struct {
Dividend int
Divisor int
}
func (e *DivideError) Error() string {
return fmt.Sprintf("cannot divide %d by %d", e.Dividend, e.Divisor)
} //扩展Error方法从而达到自定义错误的效果
func divide(a, b int) (int, error) {
if b == 0 {
return 0, &DivideError{Dividend: a, Divisor: b}
}
return a / b, nil
}
func main() {
_, err := divide(10, 0)
if err != nil {
fmt.Println(err) // 输出:cannot divide 10 by 0
}
}
2.panic 和 recover
Go 的 panic 用于处理不可恢复的错误,recover 用于从 panic 中恢复。
panic:
- 导致程序崩溃并输出堆栈信息。
- 常用于程序无法继续运行的情况。
recover:
-
捕获
panic
,避免程序崩溃。package main import "fmt" func safeFunction() { defer func() { //defer用于在函数返回之前延迟执行一个函数的调用,无论函数是正常返回还是由于panic导致的非正常返回,defer都会执行,用于释放资源,关闭文件 if r := recover(); r != nil { //recover用于捕获panic错误防止程序崩溃并能正常运行 fmt.Println("Recovered from panic:", r) } }() panic("something went wrong") } func main() { fmt.Println("Starting program...") safeFunction() fmt.Println("Program continued after panic") }
十九:Go并发
并发是指程序同时执行多个任务的能力。Go通过采用goroutines 和 channels 实现并发
1.Goroutines
Go中并发执行的单位,类似于轻量级的线程,Goroutine的调度由Go运行时管理,无需手动分配线程,使用go关键字启动goroutine,非阻塞的。
go func_name (参数列表)
Go允许使用go语句开启一个新的线程,即goroutine,以一个不同的,新创建的goroutine来执行一个函数,同一个程序中的所有goroutine共享同一个地址空间
package main
import (
"fmt"
"time"
)
func sayhello(){
for i := 0 ; i < 5 ; i++ {
fmt.Println("Hello")
time.Sleep(100 * time.Millisecond)
}
}
func main(){
go sayhello()
fot i := 0 ; i < 5 ; i++{
fmt.Println("Main")
time.Sleep(100 * time.Millisecond)
}
}
2.通道channel
Go中用于在goroutine中通信的机制,支持同步和数据共享,使用chan关键字创建,用 <- 发送和接受数据
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
ch := make(chan int) //建立一个int类型的通道
ch <- v //把v发送给通道ch
v := <- ch //从ch接收数据 并把值赋给v
默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
package main
import "fmt"
func sum(s []int , c chan int){
sun := 0
for _, v := range s{
sum += v
}
c <- sum
}
func main(){
s := []int{1 , 2, 3 ,4 ,5 ,6}
c := make(chan int)
go sum(s[len(s)/2:], c)
go sum(s[:len(s)/2], c)
x , y := <-c , <- c
fmt.Println(x,y,x+y)
}
3.通道缓冲区
ch := make(chan int , 2) //缓冲区大小为2
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
带缓冲区的通道可以使数据的发送端和接收端异步工作,发送方可以一直发送,知道缓冲区满,接收端不必立刻接受,可以等想接受的接收再进行接收
4.range遍历通道
package main
import "fmt"
func foo(n int, c chan int){
x , y := 0 ,1
for i := 0 ; i < n ; i++{
c <- x
x , y = y , x + y
}
close(c)
}
func main(){
c := make(chan int, 10)
go foo(cap(c),c)
for i := range c{
fmt.Println(i)
}
}
5.Select语句
select会选中一个通道执行,如果没有可以执行的通道,则会阻塞
package main
import "fmt"
func foo( c,quit chan int){
x , y := 0 ,1
for{
select{
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return //一定要提前结束select语句,否则会发生死锁
}
}
}
func main(){
c := make(chan int)
quit := make(chan int)
go func(){
for i:=0; i< 10 ;i++{
fmt.Println(<-c)
}
quit <- 0
}()
foo(c,quit)
}
6.sync.WaitGroup等待所有goroutine完成
package main
import(
"fmt"
"sync"
)
func worker(id int,wg *sync.WaitGroup){
fmt.Printf("%d\n",id)
fmt.Printf("%d\n",id)
defer wg.Done()
}
func main(){
var wg sync.WaitGroup
for i := 1; i < 3; i++{
wg.Add(1)
go worker(i , &wg)
}
wg.Wait()
fmt.Println("All goroutine Finished")
}