目录
一、变量
在go中声明变量有多种语法。
1.1声明变量
var名称类型是声明单个变量的语法。
以字母或下划线开头,由一个或多个字母、数字、下划线组成
声明一个变量
第一种, 指定变量类型,声明后若不赋值,使用默认值
var name type name = value
第二种,根据值自行判定变量类型(类型推断Type inference)
如果一个变量有一个初始值,Go将自动能够使用初始值来推断该变量的类型,因此,如果变量具有初始值,则可以省略变量声明中的类型。
var name=value
第三种,省略var, 注意 := 左侧的变量不应该是已经声明过的(多个变量同时声明时,至少保证一个是新变量), 否则会导致编译错误(简短声明)
这种方式只能被用在函数体内,而不可以用二全局变量的声明与赋值。
多变量声明
第一种, 以逗号分隔,声明与赋值分开, 若不赋值,存在默认值
var name1, name2, name3 type name1, name2, name3 = v1, v2, v3
第二种, 直接赋值,下面的变量类型可以是不同的类型
var name1, name2, name3=v1,v2,v3
第三种, 集合类型
var ( name1 type1 name2 type2 )
package main import "fmt" func main() { /* 变量: variable 概念:一小块内存,用于存储数据,在程序运行过程中数值可以改变 使用: step1: 变量的声明,也叫定义 第一种: var 变量名 = 数据类型 变量名 = 赋值 第二种: 类型推断, 省略数据类型 var 变量名 = 赋值 第三种: 简短声明, 省略var 变量名 := 赋值 step2: 变量的访问,赋值和取值 直接根据变量名访问 使用指针地址访问 go特性: 静态语言: 强类型语言 go java c++ c# 动态语言: 弱类型语言 javascipt php python ruby */ //第一种:定义变量,然后进行赋值 var num1 int num1 = 30 fmt.Printf("num1的数值是%d\n", num1) var num2 int = 15 fmt.Printf("num2的数值是 %d\n", num2) //第二种:类型推断 var name = "Jack" fmt.Printf("类型是: %T, 数值是%s\n", name, name) //第三种:简短定义,也叫简短声明 sum := 200 fmt.Println(sum) //多个变量同时定义 var a, b, c int a = 1 b = 2 c = 3 fmt.Println(a, b, c) var m, n int = 100, 300 fmt.Println(m, n) var n1, f1, s1 = 100, 3.14, "longlongago" fmt.Println(n1, f1, s1) var ( studentName = "李小明" age = 18 sex = "男" ) fmt.Printf("学生姓名: %s; 年龄: %d;性别: %s\n", studentName, age, sex) }
使用&变量名,访问内存地址:
var num int num = 100 fmt.Printf("num的数值是: %d, 地址是: %p\n", num, &num) num = 200 fmt.Printf("num的数值是: %d, 地址是: %p\n", num, &num)
注意事项:
变量必须先定义才能使用。
go语言是静态语言,要求变量的类型和赋值的类型必须一致。
变量名不能冲突,(同一个作用域内不能冲突)
简短定义方式,左边的变量名至少有一个是新的。
简短定义方式,不能定义全局变量。
变量的零值,也叫默认值
变量定义了就要使用,否则无法通过编译。
二、常量
1.常量声明
常量是一个简单的标识符,在程序运行时,不会被修改的量。
const identifiter [type] = value
显式类型定义: const b string="abc" 隐式类型定义: const b = "abc"
func main() { /* 常量: 1.概念:同变量类似,程序执行过程中数值不能改变 2.语法: 显式类型定义 隐式类型定义 3.常数 固定的数值: 100 ”abc“ */ fmt.Println(100) fmt.Println("abc") //1.定义常量 const PATH string = "http://www.baidu.com" const PI = 3.14 fmt.Println(PATH) fmt.Println(PI) //2.尝试修改常量的数值 //PATH="http://www.sina.com" //3.定义一组常量 const C1, C2, C3 = 100, 3.14, "哈哈" const ( MALE = 0 FEMALE = 1 UNKNOW = 3 ) //4.一组常量中,如果某个常量没有初始值,默认和上一行一致 const ( a int = 100 b c string = "ruby" d e ) fmt.Printf("%T, %d\n", a, a) fmt.Printf("%T, %d\n", b, b) fmt.Printf("%T, %s\n", c, c) fmt.Printf("%T, %s\n", d, d) fmt.Printf("%T, %s\n", e, e) //5.枚举类型,使用常量组作为枚举类型,一组相关数值的数据 const ( SPRING = 0 SUMMER = 1 AUTUMN = 2 WINTER = 3 ) }
常量的注意事项:
常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串
不管使用的常量,在编译的时候,是不会报错的。
显示指定类型的时候,必须确保常量左右值类型一致,需要时可做显示类型转换。 这与变量就不一样了,变量是可以是不同的类型值。
三、iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量,iota可以被用做枚举值。
package main import "fmt" func main() { /* iota:特殊的常量,要以被编译器自动修改的常量 每当定义一个const, iota初始值为0 每当定义一个常量,就会自动累加1 直到下一个const出现,清零 */ const ( a = iota b = iota c = iota ) fmt.Println(a) fmt.Println(b) fmt.Println(c) const ( d = iota e ) fmt.Println(d) fmt.Println(e) //枚举中 const ( male = iota female unknow ) fmt.Println(male, female, unknow) }
const ( A = iota B C D = "haha" E F = 100 G H = iota I ) const ( j = iota ) fmt.Println(A) fmt.Println(B) fmt.Println(C) fmt.Println(D) fmt.Println(E) fmt.Println(F) fmt.Println(H) fmt.Println(I) fmt.Println(j)
三、基本数据类型
1.bool类型
2.数值类型
package main import "fmt" func main() { /* Go语言的数据类型: 1.基本数据类型 布尔类型: bool 取值: true, false 数值类型: 整数: int 有符号: 最高位表示符号位,0正数,1负数,其余位表示数值 int8: (-128 至 127) int16: (-32768 至 32767) int32: (-2147483648 至 2147483647) int64: (-9223372036854775808 至 9223372036854775807) 无符号:所有的位表示数值 uint8: (0 至 256) uint16: (0 至 65535) uint32: (0 至 4294967295) uint64: (0 至 18446744073709551615) int uint byte:uint8 rune:int32 浮点: 生活中的小数 float32, float64 复数: complex 字符串:string 2.复合数据类型 array、slice、 map、 function、 pointer、 struct、 interface、 channel */ //1、布尔类型 var b1 bool b1 = true fmt.Printf("%T, %t\n", b1, b1) b2 := false fmt.Printf("%T, %t\n", b2, b2) //2.整数 var i1 int8 i1 = 100 fmt.Println(i1) var i2 uint8 i2 = 200 fmt.Println(i2) var i3 int i3 = 1000 fmt.Println(i3) //语法角度:int , int64 不认为是同一种类型 // var i4 int 64 // i4 == i3 //cannot use i3 (type int) as type int64 in assignment var i5 uint8 i5 = 100 var i6 byte i6 = i5 fmt.Println(i5, i6) var i7 = 100 fmt.Printf("%T, %d\n", i7, i7) //浮点 var f1 float32 f1 = 103.14 var f2 float64 f2 = 2066.25 fmt.Printf("%T, %f\n", f1, f1) //默认保留六位小数 fmt.Printf("%T, %f\n", f2, f2) //默认保留六位小数 fmt.Printf("%T, %.2f\n", f1, f1) //限制保留两位小数 fmt.Printf("%T, %.3f\n", f2, f2) //限制保留三位小数 fmt.Println(f1) //原样输出 var f3 = 5.12 fmt.Printf("%T, %f\n", f3, f3) //类型推断 float64 }
3.字符串
func string_demo() { /* 字符串: 1.概念: 多个byte的集合,理解为一个字母序列 2.语法:使用双引号 “abc” "hello" "A" 也可以使用'' 3.编码问题: 计算机本质只识别0和1 A:65, B:66,C:67 .... a:97, b:98, .... ASCII(美国标准信息交换码) 中国的编码表(gbk) 兼容ASCII Unicode编码表: 号称统一了全世界 utf-8 utf-16, utf-32 .... 4.转义字符:\ A: 有一些字符,有特殊的作用,可以转义为普通的字符 \' \' B: 有一些字母,就是普通字母,转义后有特殊的作用 \n, 换行符 \t, 制表符 */ //1.定义字符串 var s1 string s1 = "李二毛" fmt.Printf("%T, %s\n", s1, s1) s2 := "hello world" fmt.Printf("%T, %s\n", s2, s2) //2.区别 'A' "a" v1 := 'A' v2 := "A" fmt.Printf("%T, %d\n", v1, v1) fmt.Printf("%T,%s\n", v2, v2) v3 := '中' fmt.Printf("%T, %d, %q, %c\n", v3, v3, v3, v3) //3.转义字符 fmt.Println("\"Hello World\"") //特别的操作 fmt.Println(`He"lloWor"ld`) fmt.Println("He`lloWor`ld") }
4.数据类型转换: Type Convert
func type_convert() { /* 数据类型转换:Type Convert go语言是静态语言,定义、赋值、运算必须类型一致 语法格式: Type(Value) 注意点:兼容类型可以转换 常量:在有需要的时候,会自动转型 变量:需要手动转型 T(V) */ var a int8 = 10 var b int16 b = int16(a) fmt.Println(a, b) f1 := 5.12 var c int c = (int)(f1) fmt.Println(f1, c) f1 = float64(a) fmt.Println(f1) //b1 := true //a = int8(b1) //Cannot convert an expression of the type 'bool' to the type 'int8' }
四、运算符
1.算术运算符
func main() { /* 算术运算符: +、-、*、/、%、++、-- + - *: 乘 /: 取商, 两个数相除,取商 %: 取余, 两个数相除,取余数 ++: 自身加1 i++ --:自身减1 i-- ++与--相对与整数来计算,不支持 ++i, --i, 及在表达式中运算 */ a := 10 b := 3 sum := a + b fmt.Printf("%d + %d = %d\n", a, b, sum) sub := a - b fmt.Printf("%d - %d = %d\n", a, b, sub) mul := a * b fmt.Printf("%d * %d = %d\n", a, b, mul) div := a / b mod := a % b fmt.Printf("%d / %d = %d\n", a, b, div) fmt.Printf("%d %% %d = %d\n", a, b, mod) c := 3 c++ fmt.Println(c) c-- fmt.Println(c) }
2.关系运算符
func main() { /* 关系运算符: >、<、>=、 <=、 ==、 != 结果总是bool类型的:true、 false ==: 表示比较两个数值是相等的 !=: 表示比较两个数值是不相等的。 */ a := 3 b := 5 c := 3 res1 := a > b res2 := b > c fmt.Printf("%T,%t\n", res1, res1) fmt.Printf("%T,%t\n", res2, res2) res3 := a == b fmt.Println(res3) res4 := a == c fmt.Println(res4) fmt.Println(a != b, b != c) }
3.逻辑运算符
func main() { /* 逻辑运算符: 操作数必需是bool, 运算结果也是bool 逻辑与: && 运算规则:所有的操作数都是真,结果才为真,有一个为假,结果就为假 “一假则假,全真才真” 逻辑或:|| 运算规则:所有的操作数都是假,结果才为假,有一个为真,结果就为真 “一真为真,全假才假” 逻辑非: ! !T ---> flase !F ---> true */ f1 := true f2 := false f3 := true res1 := f1 && f2 fmt.Printf("res1: %t\n", res1) res2 := f1 && f2 && f3 fmt.Printf("res1: %t\n", res2) res3 := f1 || f2 fmt.Printf("res1: %t\n", res3) res4 := f1 || f2 || f3 fmt.Printf("res1: %t\n", res4) fmt.Printf("f1:%t, !f1:%t\n", f1, !f1) fmt.Printf("f2:%t, !f2:%t\n", f2, !f2) a := 3 b := 2 c := 5 res5 := a > b && c%a == b && a < (c/b) fmt.Println(res5) res6 := b*2 < c || a/b != 0 || c/a > b fmt.Println(res6) res7 := !(c/a == b) fmt.Println(res7) }
4.位运算符
func main() { /* 位运算符: 将数值,转为二进制后,按位操作 按位&: 对应位的值如果都为1结果才为1,有一个为0就为0 按位| 对应位的值如果都为0绪果才为0,有一个为1就为1 异或^: 二元:a^b 对应位的值不同为1,相同为0 一元:^a 按位取反: 1 ---> 0 0 ---> 1 位清空:&^ 对于 a &^ b 对于b上的每个数值 如果为0, 则取a对应位上的数值 如果为1,则结果位就取0 位移动算符: <<: 按位左移, 将a转为二进制,向左移动b位 a << b >>: 按位右移, 将a转为二进制,向右移动b位 a >> b */ a := 60 b := 13 /* 60: 0011 1100 13: 0000 1101 &: 0000 1100 12 |: 0011 1101 61 ^: 0011 0001 49 &^: 0011 0000 48 ^a: 1111 1111 1111 1111 .... 1100 0011 */ fmt.Printf("a:%d, %b\n", a, a) fmt.Printf("a:%d, %b\n", b, b) res1 := a & b fmt.Println(res1) res2 := a | b fmt.Println(res2) res3 := a ^ b fmt.Println(res3) res4 := a &^ b fmt.Println(res4) res5 := ^a fmt.Println(res5) c := 8 /* 8: 0000 0100 <<: 0001 0000 >>: 0000 0001 */ res6 := c << 2 fmt.Println(res6) res7 := c >> 2 fmt.Println(res7) }
5.赋值运算符
func main() { /* 赋值运算符: =,+=,-=, *=, /=, %=, <<=, >>=, &=, |=,^= ... =: 把=右侧的数值, 赋值给 = 左边的变量 +=, a += b, 相当于 a = a + b a++ 即 a += 1 */ var a int a = 3 fmt.Println(a) a += 4 fmt.Println(a) a -= 3 fmt.Println(a) a *= 2 fmt.Println(a) a /= 3 fmt.Println(a) a %= 1 fmt.Println(a) }
6.优先级运算符优先级
可以通过使用括号来临时提升某个表达式的整体运算优先级。
五、键盘输入输出
import ( "bufio" "fmt" "os" ) func main() { /* 输入和输出: fmt包: 输入、输出 输出: Print() //打印 Println() //打印之后换行 Printf() //格式化打印 格式化打印占位符: %v 原样输出 %T 打印类型 %t bool类型 %s 字符串 %f 浮点数 %d 10进制的整数 %b 2进制的数 %o 八进制数 %x %X 16进制 %x a-f %X A-F %c 打印字符 %p 打印地址 输入: Scanln() Scanln is similar to Scan,but stops scanning at scanf() bufio包 */ a := 100 b := 3.14 c := true d := "Hello World" e := "Ruby" f := 'A' fmt.Printf("%T, %b\n", a, a) fmt.Printf("%T, %f\n", b, b) fmt.Printf("%T, %t\n", c, c) fmt.Printf("%T, %s\n", d, d) fmt.Printf("%T, %s\n", e, e) fmt.Printf("%T,%d, %c\n", f, f, f) fmt.Println("__________________________________") fmt.Printf("%v\n", d) /* var x int var y float64 fmt.Println("请输入一个整数,一个浮点类型:") fmt.Scanln(&x,&y) //读取键盘的输入,通过操作地址,赋值给x和y,阻塞式 fmt.Printf("a的数值: %d, b的数值; %f\n", a, b) fmt.Scanf("%d, %f", &x, &y) fmt.Prinf("x:%d, y:%f\n", x, y) */ fmt.Println("请输入一个字符串:") reader := bufio.NewReader(os.Stdin) s1, _ := reader.ReadString('\n') fmt.Println("读到的数据: ", s1) }
五、流程控制
1.条件语句
(1) if 语句
func main() { /* 条件语名: if 语法格式: if 条件表达式 { // } */ //1.指定一个数字,如果大于10,就打印这个数字大于10 num := 16 if num > 10 { fmt.Println("大于10") } //2.给定一个成绩,如果大于等于60分,就打印及格 score := 75 if score > 60 { fmt.Println("成绩合格") } fmt.Println("main...over...") }
(2) if else 语句
func main() { /* if...else语句 if 条件 {50 //条件成立,执行此处的代码. A 段 }else{ //条件不成立,执行此处的代码: B 段 } 注意点: 1. if后的{, 一定是要和if条件写在同一行的 2. else一定是if语句}之后,不能自已另起一行 3. if和else中的内容,二者必选其一来执行 */ //给定一个成绩,如果大于等于60,就是及格,否则就是不及格. score := 0 fmt.Println("请输入您的成绩:") fmt.Scanln(&score) if score >= 60 { fmt.Println(score, "及格") } else { fmt.Println(score, "不及格") } sex := "男" //bool, int, string if sex == "男" { fmt.Println("可以直接玩字符串") } else { fmt.Println("你个死变态") } fmt.Println("main...over...") }
(3) if语句的嵌套
func main() { /* if语句的嵌套: if 条件1 { A 段 }else { if 条件2 { B 段 }else { C 段 } } 简写: if 条件1 { A 段 }else if 条件2 { B 段 }else if 条件3 { C 段 }... else { n 段 } */ sex := "泰国" //bool, int, string if sex == "男" { fmt.Println("你因该去男厕所...") } else { if sex == "女" { fmt.Println("正常人") } else { fmt.Println("上帝知道") } } }
(4) if语句的其他写法
package main import "fmt" func main() { /* if语句的其他写法: if 初始化语句: 条件 { } 在if中初始化后的作用域在if里 */ if num := 4; num > 0 { fmt.Println("正数", num) } else if num < 0 { fmt.Println("负数", num) } //fmt.Println(num) //Unresolved reference 'num' num2 := 5 if num2 > 0 { fmt.Println("num2, 是正数", num2) } fmt.Println(num2) }
2.switch分支语句
(1) switch语句
package main import "fmt" func main() { /* switch 语句 语法结构: switch 变量名 { case 数值1: 分支1 case 数值1: 分支1 case 数值1: 分支1 ... default: 最后一个分支 } 注意事项: 1.switch可以作用在其他类型上,case后的数值必须和switch作用的变量类型一致 2.case是无序 3.case后的数值是唯一的 4.default可选 */ num := 3 switch num { case 1: fmt.Println("第一季度") case 2: fmt.Println("第二季度") case 3: fmt.Println("第三季度") case 4: fmt.Println("第四季度") default: fmt.Println("数据错误...") } //模拟计算器 num1 := 0 num2 := 0 oper := "" fmt.Println("请输入一个整数:") fmt.Scanln(&num1) fmt.Println("请再输入一个整数:") fmt.Scanln(&num2) fmt.Println("请输入一个操作: +,-,*,/") fmt.Scanln(&oper) switch oper { case "+": fmt.Printf("%d + %d = %d\n", num1, num2, num1+num2) case "-": fmt.Printf("%d - %d = %d\n", num1, num2, num1-num2) case "*": fmt.Printf("%d * %d = %d\n", num1, num2, num1*num2) case "/": fmt.Printf("%d / %d = %d\n", num1, num2, num1/num2) } fmt.Println("main...over...") }
(2) switch其他写法
package main import "fmt" func main() { /* 省略switch后的变量,相当于直接作用在true上 switch { //true; case true: case false: ] case 后可以同时跟随多个数值 switch 变量名 { case 数值1, 数值2, 数值3: 分支1 case 数值4, 3: 分支2 } switch后可以多一条初始化语句 switch 初始化语句; 变量 { } */ switch { case true: fmt.Println("true...") case false: fmt.Println("false...") } /* [0-59]: 不及格 [60, 69]: 及格 [70, 79]: 中等 [80, 89]: 良好 [90, 100]:优秀 */ score := 88 switch { case score >= 0 && score < 60: fmt.Println(score, "不及格") case score >= 60 && score < 70: fmt.Println(score, "及格") case score >= 70 && score < 80: fmt.Println(score, "中等") case score >= 80 && score < 90: fmt.Println(score, "良好") case score >= 90: fmt.Println(score, "优秀") } fmt.Println("---------------------------------------") letter := "O" switch letter { case "A", "E", "I", "O", "U": fmt.Println(letter, "是元音....") case "M", "N": fmt.Println(letter, "M或N....") default: fmt.Println("其他") } /* 一个月的天数: 1,3,5,7,8,10,12 31 4,6,9,11 30 2: 29 or 28 */ month := 9 day := 0 year := 2019 switch month { case 1, 3, 5, 7, 8, 10, 12: day = 31 case 4, 6, 9, 11: day = 30 case 2: if year%400 == 0 || (year%4 == 0 && year%100 != 0) { day = 28 } else { day = 29 } default: fmt.Println("月份有误") } fmt.Printf("%d 年 %d 月有%d 天\n", year, month, day) fmt.Println("---------------------------------------") switch language := "golang"; language { case "golang": fmt.Println("Go语言") case "java": fmt.Println("Java语言") case "python": fmt.Println("python") } //fmt.Println(language) //undefined: language }
(3) switch中的break和fallthrough
package main import "fmt" func main() { /* switch中的break和fallthrough语句 break: 可以使用在switch中,也可以使用在for循环中, 强制结束case语句,从而结束switch分支 fallthrough: 用于穿透switch 当switch中某个case 匹配成功之后,就执行该case语句 如果遇到fallthrough, 那么后面紧邻的case, 无需匹配,执行穿透 fallthrough 应该位于某个case的最后一行 */ n := 2 switch n { case 1: fmt.Println("这里是熊大") fmt.Println("这里是熊大") fmt.Println("这里是熊大") case 2: fmt.Println("这里是熊二") fmt.Println("这里是熊二") break //用于强制结束case, 意味前瞻switch被强制结束 fmt.Println("这里是熊二") case 3: fmt.Println("这里是光头强") fmt.Println("这里是光头强") fmt.Println("这里是光头强") } fmt.Println("main... over....") fmt.Println("-----------------------------------------") m := 2 switch m { case 1: fmt.Println("第一季度") case 2: fmt.Println("第二季度") fallthrough //fmt.Println("..................") The 'fallthrough' statement is out of place case 3: fmt.Println("第三季度") case 5: fmt.Println("第四季度") } }
3.for循环语句
(1) 语句
package main import "fmt" func main() { /* 1.标准写法: for循环: 某些代码会被多次的执行 语法: for 表达式1; 表达式2; 表达式3{ 循环体 } 2.同时省略表达式1和表达式3 for 表达式2 { 循环体 } 相当于while(条件) 3.同时省略3个表达式 for{ } 相当于while(true) 注意点:当for循环中,省略了表达式2,就相当于直接作用在了true上 4. 其他的写法: for循环中同时省略几个表达式都可以 省略表达式1: 省略表达式2: 循环永远成闰--->死循环 省略表达式3: */ for i := 0; i <= 5; i++ { fmt.Println("这是循环第 次了") } k := 0 for k <= 5 { fmt.Println(k) k++ } fmt.Println("---->", k) }
(2)练习
package main import "fmt" func main() { /* for循环的练习题 练习1:打印58到23之间的数字 练习2:求1-100的和 练习3:打印1 -100内,能够被3整除,但理不能被5整除的数字,统计被打印的数字的个数,每行打印5个 */ for i := 58; i > 23; i-- { fmt.Println(i) } fmt.Println("----------------------------------------------------") var sum int = 0 for i := 1; i <= 100; i++ { sum += i } fmt.Println("1-100的和", sum) fmt.Println("------------------------------------------------") var k int = 0 for i := 0; i < 100; i++ { if i%3 == 0 && i%5 != 0 { k++ fmt.Print(i, " ") if k%5 == 0 { fmt.Println() } } } fmt.Println() fmt.Println("总共有", k, "个数字") }
(3).for多层嵌套
package main import "fmt" func main() { /* 循环嵌套:多层循环嵌套在一起 */ for j := 0; j < 5; j++ { for i := 0; i < 5; i++ { fmt.Print("*") } fmt.Println() } fmt.Println("--------------------------------------------") for i := 1; i <= 9; i++ { for j := 1; j <= i; j++ { fmt.Printf("%d * %d = %d\t ", i, j, i*j) } fmt.Println() } }
(4) break 和 continue
package main import "fmt" func main() { /* 循环结束: 循环条件不满足,循环自动结束了 但是可以通过break和continue来强制结束 循环控制语句: break: 彻底的结束循环 continue: 结束某一次循环, 下次继续,中止 注意点:多层循环嵌套,break和continue, 默认结束最近的for 如果想结束指定的某个循环,可以给循环贴标签 break 循环标签名 continue 循环标签名 */ for i := 1; i < 10; i++ { if i == 5 { break } fmt.Println(i) } fmt.Println("........................................................................") for i := 1; i <= 5; i++ { for j := 1; j <= 5; j++ { if j == 2 { //break continue } fmt.Printf("i:%d, j:%d\n", i, j) } } fmt.Println("main...over...") }
package main import ( "fmt" "math" ) func main() { /* 水仙花数: 三位数 [100- 999] 每个位上的数字的立方和, 刚好等于该数字本身,这个数字就叫水仙花数。 比如: 153 = 1^3+5^3+3^3 */ for i := 100; i <= 999; i++ { x := i / 100 y := i / 10 % 10 z := i % 10 if math.Pow(float64(x), 3)+math.Pow(float64(y), 3)+math.Pow(float64(z), 3) == float64(i) { fmt.Println("这是个水仙花数", i) } } fmt.Println("-------------------------------") for x := 1; x < 10; x++ { for y := 0; y < 10; y++ { for z := 0; z < 10; z++ { if math.Pow(float64(x), 3)+math.Pow(float64(y), 3)+math.Pow(float64(z), 3) == float64(x*100+y*10+z) { fmt.Printf("这是个水仙花数: %d%d%d\n", x, y, z) } } } } }
package main import ( "fmt" "math" ) func main() { /* 打印 2-100 内的素数(只能被1和本身能整除的) */ for i := 2; i < 100; i++ { flag := true //记录i是否是素数 //for j := 2; j < i; j++ { for j := 2; j <= int(math.Sqrt(float64(i))); j++ { if i%j == 0 { flag = false break } } if flag { fmt.Println("是素数", i) } } }
4.goto语句
func main() { /* goto语句 */ var a = 10 LOOP: for a < 20 { if a == 15 { a += 1 goto LOOP } fmt.Println("a的值为:", a) a++ } fmt.Println("----------------------------------------------------------") for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { if j == 2 { goto breakHere } } } //手动返回, 避免执行进入标签 return breakHere: fmt.Println("done....") }
5.生成随机数
package main import ( "fmt" "math/rand" "time" ) func main() { /* 生成随机数random 伪随机数,根据一定的算法公式算出来的, math/rand */ num1 := rand.Int() fmt.Println(num1) for i := 0; i < 10; i++ { num := rand.Intn(10) fmt.Print(num) } fmt.Println("---------------------------------------------------------------") rand.NewSource(100) //改变种子数 num2 := rand.Intn(10) fmt.Println(num2) t1 := time.Now() fmt.Println(t1) fmt.Printf("%T\n", t1) //时间戳 timeStamp1 := t1.Unix() fmt.Println("millisecond ", timeStamp1) timeStamp2 := t1.UnixNano() fmt.Println("millisecond ", timeStamp2) timeStamp3 := t1.UnixMicro() fmt.Println("millisecond ", timeStamp3) timeStamp4 := t1.UnixMilli() fmt.Println("millisecond ", timeStamp4) //setp1: 设置种子数 rand.NewSource(time.Now().UnixNano()) for i := 0; i < 10; i++ { fmt.Println("---->", rand.Intn(100)) } num3 := rand.Intn(46) + 3 fmt.Println("----> ", num3) }
附:Package rand - The Go Programming Language
六、复合数据类型
1.数组
(1) 语法
package main import "fmt" func main() { /* 数据类型: 基本类型:整型、浮点、布尔、字符串 复合类型:array、slice、map、struct、pointer、function、channel ... 数组: 1.概念: 存储一组相同数据类型的的数据结构 2.语法 var 数组名 [长度] 数据类型 var 数组名 = [长度] 数据类型 {元素1,元素2,....} 数据名 := [...] 数据类型 {元素....} 3.通过下标访问 下标, 也叫索引, index 默认从0开始的整数,直到长度减1 数组名[index] 赋值 取值 不能越界 [0, 长度 - 1] 4.长度和容量: go语言的内置函数 len(array/map/slice/string). 长度 cap(), 容量 */ var num1 int num1 = 100 num1 = 200 fmt.Println(num1) fmt.Printf("变量的内存地址: %p\n", &num1) //step 1: 创建数组 var arr1 [4]int fmt.Printf("数组的内存地址:%p\n", &arr1) //step 2:数组的访问 arr1[0] = 1 arr1[1] = 2 arr1[2] = 3 arr1[3] = 4 fmt.Println(arr1[0]) fmt.Println(arr1[2]) //fmt.Println(arr1[4]) //invalid argument: index 4 out of bounds [0:4] fmt.Println("数组的长度:", len(arr1)) //容器中实际存储的数据量 fmt.Println("数组的容量:", cap(arr1)) //容器中能够存储的最大的数量 //因为数组室长,长度和容量相同 arr1[0] = 100 fmt.Println(arr1[0]) //数组的其他创建方式 var a [4]int //同 var a = [4] int fmt.Println(a) var b = [4]int{1, 2, 3, 4} fmt.Println(b) var c = [5]int{1, 2, 4} fmt.Println(c) var d = [5]int{1: 1, 3: 2} fmt.Println(d) var e = [5]string{"rose", "王二狗", "rust"} fmt.Println(e) f := [...]int{1, 2, 3, 4, 5} fmt.Println(f) fmt.Println(len(f)) g := [...]int{1: 3, 6: 5} fmt.Println(g) fmt.Println(len(g)) }
(2) 数组遍历
package main import "fmt" func main() { /* 数组的遍历: 依次访问数组中的元素 方法一: arr[0], arr[1], arr[2] 方法二: 通过循环,配合下标 for i:=0;i<len(arr);i++ { arr[i] } 方法三: 使用range range, 词议“范围” 不需要操作数组的下标,到达数组的末尾, 每次都数组中获取下标和对应的数值 */ arr1 := [5]int{1, 2, 3, 4, 5} fmt.Println(arr1[0]) fmt.Println(arr1[1]) fmt.Println(arr1[2]) fmt.Println(arr1[3]) fmt.Println(arr1[4]) fmt.Println("---------------------------------------------------------------------") for i := 0; i < len(arr1); i++ { arr1[i] = i*2 + 1 fmt.Println(arr1[i]) } fmt.Println("-----------------------") for index, value := range arr1 { fmt.Printf("下标是:%d, 数值是:%d\n", index, value) } sum := 0 for _, v := range arr1 { sum += v } fmt.Println("数组的和是: ", sum) }
(3) 数组传递类型
package main import "fmt" func main() { /* 数据类型: 基本类型:int、float、bool、string 复合类型:array、slice、map、struct、pointer、function、channel ... 数组的数据类型: [size] type 值类型:理解为存储的数值本身 将数值传递给其他的变量,传递的是数据的副本(备份) int, float, string, bool, array 引用类型:存储的娄据的内存地址 slice, map */ //1.数据类型 num := 10 fmt.Printf("%T, %d\n", num, num) arr1 := [4]int{1, 2, 3, 4} arr2 := [3]float64{2015, 3.18, 6.15} arr3 := [4]int{5, 6, 7, 8} arr4 := [2]string{"hello", "world"} fmt.Printf("arr1类型是: %T\n", arr1) //[4]int fmt.Printf("arr2类型是: %T\n", arr2) //[3]float64 fmt.Printf("arr3类型是: %T\n", arr3) //[4]int fmt.Printf("arr4类型是: %T\n", arr4) //[2]string //2.赋值 num2 := num //值传递 fmt.Println(num, num2) //10 10 num2 = 20 fmt.Println(num, num2) //10 20 //数组是值传递 arr5 := arr1 fmt.Println(arr1) //[1 2 3 4] fmt.Println(arr5) //[1 2 3 4] arr5[0] = 200 fmt.Println(arr1) //[1 2 3 4] fmt.Println(arr5) //[200 2 3 4] a := 3 b := 4 fmt.Println(a == b) fmt.Println(arr5 == arr1) }
(4)数组排序
package main import "fmt" func main() { /* 数组的排序:升序,降序 排序的算法: 冒泡排序、插入排序、选择排序、希尔排序、堆排序、快速排序.... 冒泡排序(Bubble sort) 依次比较两个相邻的元素,把他们的按需要的顺序交换过来 */ arr := [5]int{15, 23, 8, 19, 7} fmt.Println(arr) for i := 1; i < len(arr); i++ { for j := 0; j < len(arr)-i; j++ { if arr[j] > arr[j+1] { arr[j], arr[j+1] = arr[j+1], arr[j] } } } fmt.Println(arr) }
(5)多维数组
package main import "fmt" func main() { /* 一维数组:存储的多个数据是数值本身 a1 := [3] int {1,2,3} 二维数组:存储的是一堆一维 a2 := [3][4] int { {}, {}, {}} 该二维数组的长度,就是3 存储的元素是一维数组,一维数组的元素是数值,每个一给数组长度为4. 多维数组: */ a2 := [3][4]int{ {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}} fmt.Println(a2) fmt.Printf("二维数组的地址:%p\n", &a2) fmt.Printf("二维数组的长度: %d\n", len(a2)) fmt.Printf("一维数组的长度: %d\n", len(a2[0])) //遍历二维数组 for i := 0; i < len(a2); i++ { for j := 0; j < len(a2[i]); j++ { fmt.Print(a2[i][j], "\t") } fmt.Println() } fmt.Println("-----------------------------------------------------") for _, arr := range a2 { for _, val := range arr { fmt.Print(val, "\t") } fmt.Println() } }
2.slice
(1) 切片的创建
package main import "fmt" func main() { /* 数组array: 存储一组相同数据类型的数据结构。 特点:定长 切片slice: 同数组类似,也叫做变长数组或者动态数组。 特点:变长 是一个引用类型的容器,指向了一个底层数组。 make() func make(t Type, size ... IntegerType) Type 第一个参数: 类型 slice、map、chan 第二个参数: 长度len 实际存储元素的数量 第三个参数:容量cap 最多能够存储的元素的数量 func make(t Type, size ...IntegerType) Type The make built-in function allocates and initializes an object of type slice, map, or chan (only). Like new, the first argument is a type, not a value. Unlike new, make's return type is the same as the type of its argument, not a pointer to it. The specification of the result depends on the type: Slice: The size specifies the length. The capacity of the slice is equal to its length. A second integer argument may be provided to specify a different capacity; it must be no smaller than the length. For example, make([]int, 0, 10) allocates an underlying array of size 10 and returns a slice of length 0 and capacity 10 that is backed by this underlying array. Map: An empty map is allocated with enough space to hold the specified number of elements. The size may be omitted, in which case a small starting size is allocated. Channel: The channel's buffer is initialized with the specified buffer capacity. If zero, or the size is omitted, the channel is unbuffered. func append(slice []Type, elems ...Type) []Type The append built-in function appends elements to the end of a slice. If it has sufficient capacity, the destination is resliced to accommodate the new elements. If it does not, a new underlying array will be allocated. Append returns the updated slice. It is therefore necessary to store the result of append, often in the variable holding the slice itself: slice = append(slice, elem1, elem2) slice = append(slice, anotherSlice...) As a special case, it is legal to append a string to a byte slice, like this: slice = append([]byte("hello "), "world"...) */ //1.数组 arr := [4]int{1, 2, 3, 4} //定长 fmt.Println(arr) //2.切片 var s1 []int fmt.Println(s1) s2 := []int{1, 2, 3, 4} //变长 fmt.Println(s2) fmt.Printf("%T, %T\n", arr, s2) s3 := make([]int, 3, 8) fmt.Println(s3) fmt.Printf("容量: %d, 长度: %d\n", cap(s3), len(s3)) s3[0] = 1 s3[1] = 2 s3[2] = 3 fmt.Println(s3) //fmt.Println(s3[3]) //panic: runtime error: index out of range [3] with length 3 //append() s4 := make([]int, 0, 5) fmt.Println(s4) s4 = append(s4, 1, 2) fmt.Println(s4) s4 = append(s4, 3, 4, 5, 6, 7) fmt.Println(s4) s4 = append(s4, s3...) //遍历切片 for i := 0; i < len(s4); i++ { fmt.Println(s4[i]) } for i, v := range s4 { fmt.Printf("%d---->%d\n", i, v) } }
(2).slice切片扩容
package main import "fmt" func main() { /* 切片Slice: 1.每一个切片引用了一个底层数组 2.切片本身不存储任何数据,都是这个底层数组存储,所以修改切片也就是修改这个数组中的数据 3.当向切片中添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增长) 4.切片一旦扩容,就是重新指向一个新的底层数组 */ s1 := []int{1, 2, 3} fmt.Println(s1) fmt.Printf("len: %d, cap:%d\n", len(s1), cap(s1)) fmt.Printf("%p\n", s1) s1 = append(s1, 4, 5) fmt.Println(s1) fmt.Printf("len: %d, cap:%d\n", len(s1), cap(s1)) fmt.Printf("%p\n", s1) s1 = append(s1, 6, 7, 8) fmt.Println(s1) fmt.Printf("len: %d, cap:%d\n", len(s1), cap(s1)) fmt.Printf("%p\n", s1) s1 = append(s1, 9, 10) fmt.Println(s1) fmt.Printf("len: %d, cap:%d\n", len(s1), cap(s1)) fmt.Printf("%p\n", s1) s1 = append(s1, 11, 12, 13, 14, 15, 16) fmt.Println(s1) fmt.Printf("len: %d, cap:%d\n", len(s1), cap(s1)) fmt.Printf("%p\n", s1) }
(3)在已有数组上面创建切片
package main import "fmt" func main() { /* slice := arr[start:end] 切片中的数据: [start, end) arr[:end], 从到尾end 从已有的数组上,直接创建切片,该切片的底层数组就是当前的数组。 长度是从start到end切割的数据量。 但是容量从start到数组的末尾。 */ a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println("--------------------------1. 已有数组直接创建切片----------------------") s1 := a[:5] s2 := a[3:8] s3 := a[5:] s4 := a[:] fmt.Println("a:", a) fmt.Println("s1:", s1) fmt.Println("s2:", s2) fmt.Println("s3:", s3) fmt.Println("s4:", s4) fmt.Printf("%p\n", &a) fmt.Printf("%p\n", s1) fmt.Println("--------------------------2.长度和容量-----------------------------") fmt.Printf("s1 len:%d,cap: %d\n", len(s1), cap(s1)) fmt.Printf("s2 len:%d,cap: %d\n", len(s2), cap(s2)) fmt.Printf("s3 len:%d,cap: %d\n", len(s3), cap(s3)) fmt.Printf("s4 len:%d,cap: %d\n", len(s4), cap(s4)) fmt.Println("--------------------------3.更改数组的内容--------------------------") a[4] = 100 fmt.Println("s1:", s1) fmt.Println("s2:", s2) fmt.Println("s3:", s3) fmt.Println("s4:", s4) fmt.Println("--------------------------4.更改切片的内容---------------------------") s2[2] = 200 fmt.Println("s1:", s1) fmt.Println("s2:", s2) fmt.Println("s3:", s3) fmt.Println("s4:", s4) fmt.Println("--------------------------5.添加元素-------------------------------") s1 = append(s1, 1, 1, 1, 1) fmt.Println("s1:", a) fmt.Println("s1:", s1) fmt.Println("s2:", s2) fmt.Println("s3:", s3) fmt.Println("s4:", s4) }
(4).切片的数据类型---切片是引用类型数据
package main import "fmt" func main() { /* 按照特点来分: 值类型: int float string bool array 传递的是数据副本 引用类型: slice 传递的地址,多个变量指向了同一块内存地址 切片是引用类型里的数据,存储了底层数组的引用。 */ //1.数组: 值类型 a1 := [4]int{1, 2, 3, 4} a2 := a1 //值传递:传递的是数据 fmt.Println(a1, a2) a1[0] = 100 fmt.Println(a1, a2) //2.切片: 引用类型 s1 := []int{1, 2, 3, 4} s2 := s1 fmt.Println(s1, s2) s1[0] = 100 fmt.Println(s1, s2) fmt.Printf("%p\n", s1) fmt.Printf("%p\n", s2) fmt.Printf("%p\n", &s1) fmt.Printf("%p\n", &s2) }
(5) 深拷贝和浅拷贝
package main import "fmt" func main() { /* 深拷贝: 拷贝是的数据本身 值类型的数据,默认都是深拷贝:array, int,float, string, bool, struct 浅拷贝:拷贝的是数据地址 导致多个变量指向同一块内存 引用类型的数据, 默认都是浅拷贝: slice, map 因为切片是引用类型的数据,直接拷贝的是地址 func copy(dst, src []Type) int 可以实现切片的拷贝 */ s1 := []int{1, 2, 3, 4} s2 := make([]int, 0) //len:0, cap:0 //循环复制 for i := 0; i < len(s1); i++ { s2 = append(s2, s1[i]) } fmt.Println(s1) fmt.Println(s2) s1[0] = 100 fmt.Println(s1) fmt.Println(s2) //copy s3 := []int{7, 8, 9} s4 := []int{7, 8, 9} fmt.Println(s2) fmt.Println(s3) copy(s2, s3) //将s3中的元素,拷贝到s2中 copy(s3, s1[2:]) //拷贝s1的一部分到s3中去,从下标2开始 copy(s4[1:], s1[2:]) //从下标1开始覆盖 fmt.Println(s2) fmt.Println(s3) fmt.Println(s4) }
3.map
(1) map基本操作
package main import "fmt" func main() { /* map:映射, 是一种专门用于存储键值对的集合.属于引用类型 存储特点: A: 存储的是无序的键值对 B:键不能重复,并且和value值一一对应的。 map中的key不能重复,如果重复,那么新value会覆盖原来的,程序不会报错。 语法结构: 1.创建map var map1 map[key类型] value类型 nil map, 无法直接使用 var map2 = make(map[key类型]value类型) var map3 = map[key类型]value类型{key:value, key:value, ...} 2. 添加/删除 map[key] = value 如果key不存在,就是添加数据 如果key存在,就是修改数据 3. 获取 map[key] -> value value, ok := map[key] 根据key获取对应的value 如果key存在,value就是对应的数据,ok为true 如果key不存在,value就是值类型的默认值,ok为false 4. 删除 delelte(map, key) 如果key存在,就直接删除 如果key不存在, 删除失败 5. 长度 len(map) 每种数据类型: int:0 flaot: 0.0 -> 0 string: "" array: [00000] slice: nil map: nil */ //1. 创建map var map1 map[int]string //没有初始化, nil var map2 = make(map[int]string) var map3 = map[string]int{"Go": 98, "Phthon": 87, "Java": 84, "Html": 96} fmt.Println(map1) fmt.Println(map2) fmt.Println(map3) fmt.Println(map1 == nil) fmt.Println(map2 == nil) fmt.Println(map3 == nil) //map1[1] = "hello" //panic: assignment to entry in nil map fmt.Println(map1 == nil) fmt.Println(map2 == nil) fmt.Println(map3 == nil) //2. nil map if map1 == nil { map1 = make(map[int]string) fmt.Println(map1 == nil) } //3.存储键值对到map中 //map1[key] = value map1[1] = "hello" map1[2] = "world" map1[3] = "memeda" map1[4] = "张三" map1[5] = "马o" //4.获取数所,根据key获取对应的value值 //根据key获取对应的value,如果key存在,获取数值,如果key不存在,获取的是value值类型是零值 fmt.Println(map1) fmt.Println(map1[4]) //根据key为4,获取对应的value值 fmt.Println(map1[40]) //“” v1, ok := map1[40] if ok { fmt.Println("对应的数值是:", v1) } else { fmt.Println("操作的key不存在,获取的是零值", v1) } //5. 修改数据 fmt.Println(map1) map1[3] = "如花" fmt.Println(map1) //6.删除数据 delete(map1, 3) fmt.Println(map1) delete(map1, 30) fmt.Println(map1) //7.长度 fmt.Println(len(map1)) }
(2) Map的遍历
package main import ( "fmt" "sort" ) func main() { /* map的遍历: 使用: for range 数组、切片:index, value map: key, value */ map1 := make(map[int]string) map1[1] = "红孩儿" map1[2] = "小钻风" map1[3] = "白骨精" map1[4] = "白素贞" map1[5] = "金角大王" map1[6] = "王二狗" //1.遍历 for k, v := range map1 { fmt.Println(k, v) } fmt.Println("----------------------------------------------------") for i := 1; i <= len(map1); i++ { fmt.Println(i, "---->", map1[i]) } /* 1.获取所胡的key, --> 切片,数组 2.进行排序 3.遍历key, ---> map[key] */ keys := make([]int, 0, len(map1)) fmt.Println(keys) for k, _ := range map1 { keys = append(keys, k) } fmt.Println(keys) //使用冒泡排序,或者使用sort包下的排序方法 sort.Ints(keys) fmt.Println(keys) for _, key := range keys { fmt.Println(key, map1[key]) } s1 := []string{"Apple", "Windows", "Orange", "abc", "王二狗", "acd"} fmt.Println(s1) sort.Strings(s1) fmt.Println(s1) }
(3)Map结合Slice
package main import "fmt" func main() { /* map和slice的结合使用 1.创建map用于存储人的信息 name, age ,sex, address 2.每个map存储一个人的信息 3.将这些map存入到slice中 4.打印遍历输出 */ //1.创建map存储第一个人的信息 map1 := make(map[string]string) map1["name"] = "王二狗" map1["age"] = "30" map1["sex"] = "男性" map1["address"] = "西安市雁塔区某某街道" fmt.Println(map1) //2. 第二个人 map2 := make(map[string]string) map2["name"] = "李小花" map2["age"] = "20" map2["sex"] = "女性" map2["address"] = "郑州市" //3. map3 := map[string]string{"name": "ruby", "age": "38", "sex": "女性", "address": "杭州市"} fmt.Println(map3) //将map存入到slice中 s1 := make([]map[string]string, 0, 3) s1 = append(s1, map1) s1 = append(s1, map2) s1 = append(s1, map3) //遍历切片 for i, val := range s1 { fmt.Printf("第%d个人的信息是\n", i) fmt.Printf("\t姓名: %s\n", val["name"]) fmt.Printf("\t年龄: %s\n", val["age"]) fmt.Printf("\t性别: %s\n", val["sex"]) fmt.Printf("\t地址: %s\n", val["address"]) } }
(4) Map是引用类型
package main import "fmt" func main() { /* 一:数据类型 基本数据类型: int, float, string, bool 复合数据类型: array, slice, map, function,pointer, struct... array: [size]数据类型 slice: [] 数据类型 map: map[key的类型]value的类型 二: 存储特点: 值类型: int, float, string, bool, array, struct 引用类型: slice, map */ map1 := make(map[int]string) map2 := make(map[string]float64) fmt.Printf("%T\n", map1) fmt.Printf("%T\n", map2) map3 := make(map[string]map[string]string) m1 := make(map[string]string) m1["name"] = "王二狗" m1["age"] = "30" m1["salary"] = "3000" map3["hr"] = m1 m2 := make(map[string]string) m2["name"] = "ruby" m2["age"] = "28" m2["salary"] = "8000" map3["总经理"] = m2 fmt.Println(map3) fmt.Println("--------------------------------------") map4 := make(map[string]string) map4["王二狗"] = "矮矬穷" map4["李小花"] = "白富美" map4["ruby"] = "住在隔壁" fmt.Println(map4) map5 := map4 fmt.Println(map5) map5["王二狗"] = "高富帅" fmt.Println(map4) fmt.Println(map5) }
4.string
(1) string的使用
被定义为基本数据类型
package main import "fmt" func main() { /* Go中的字符串是一个字节的切片 可能通过将其内容封装在""中来创建字母,Go中的字符串是Unicode兼容的,并且是UTF-8的编码 字符串是一些字节的集合 理解为一个字黏土的序列 每个字符都有固定的位置(索引、下标、index、从0开始中,到长度-1结束) 语法:"",`` "" "a" "b" "中" "abc", "hello" 字符: ----> 对应编码表中的编码值 A---> 65 b===> 66 a--->97 字节: byte -->uint8 uft8 中文占三个字节 */ //1. 定义字符串 s1 := "hello中国" s2 := `hello world` //2.字符串的长度:返回的是字节的个数 fmt.Println(len(s1)) fmt.Println(len(s2)) //3.获取某个字节 fmt.Println(s2[0]) //获取字符串中的第一个字节 a := 'h' b := 104 fmt.Printf("%c, %c,%c\n", s2[0], a, b) //4.字符串的遍历 for i := 0; i < len(s2); i++ { fmt.Printf("%c\t", s2[i]) } //for range for _, v := range s2 { fmt.Printf("%c", v) } fmt.Println() //5.字符串是字节的集合 slice1 := []byte{65, 66, 67, 68, 69} s3 := string(slice1) //根据一个字节切片,构建字符串 fmt.Println(s3) s4 := "abcdef" slice2 := []byte(s4) //根据字符串, 获取对应的字节切片 fmt.Println(slice2) //6.字符串不能修改 fmt.Println(s4) //s4[2] = 'B' //cannot assign to s4[2] (value of type byte) }
(2) string包的使用
Package strings - The Go Programming Language
package main import ( "fmt" "strings" ) func main() { /* strings 包下关于字符串的函数 */ s1 := "helloworld" //1.是否包含指定的内容-->bool fmt.Println(strings.Contains(s1, "abc")) //2.是否包含chars中任意的一个字符即可 fmt.Println(strings.ContainsAny(s1, "abcd")) //3.统计substr在s中出现的次数 fmt.Println(strings.Count(s1, "lloo")) //4.以xxx前缀开头,以xxx后缀结尾 s2 := "20230504学习笔记.txt" if strings.HasPrefix(s2, "2023") { fmt.Println("2023年的文档") } if strings.HasSuffix(s2, ".txt") { fmt.Println("文本文档") } //5.索引 //helloworld fmt.Println(strings.Index(s1, "l")) //查找substr在s中的位置,如果不存在就返回-1 fmt.Println(strings.IndexAny(s1, "abcdefh")) //查找chars中任意的一个字符,出现在s当中的位置 fmt.Println(strings.LastIndex(s1, "l")) //查找substr在s中最后一次出现的位置 //6.字符串的拼接 ss1 := []string{"abc", "world", "hello", "ruby"} s3 := strings.Join(ss1, "*") fmt.Println(s3) //7.切割 s4 := "123,4563,aaa,49595,45" ss2 := strings.Split(s4, ",") //fmt.Println(ss2) for i := 0; i < len(ss2); i++ { fmt.Println(ss2[i]) } //8.重复, 自己拼接自己count次 s5 := strings.Repeat("hello", 5) fmt.Println(s5) //9.替换 //helloworld s6 := strings.Replace(s1, "l", "*", 1) //数字是替换几次, -1 表示全替换 fmt.Println(s6) s7 := "hello WOrLD**123....." fmt.Println(strings.ToLower(s7)) fmt.Println(strings.ToUpper(s7)) /* 10截取子串: substring(start, end) --> substr str[start:end) --> substr 包含start, 不包含end下标 */ fmt.Println(s1) s8 := s1[0:5] fmt.Println(s8) fmt.Println(s1[:5]) fmt.Println(s1[5:]) }
(3) strconv包使用
Package strconv - The Go Programming Language
package main import ( "fmt" "strconv" ) func main() { /* strconv包:字符串和基本类型之间的转换 string convert */ //fmt.Println("aa" + 100) //1.bool类型 s1 := "true" b1, err := strconv.ParseBool(s1) if err != nil { fmt.Println(err) return } fmt.Printf("%T, %t\n", b1, b1) ss1 := strconv.FormatBool(b1) fmt.Printf("%T, %s\n", ss1, ss1) //2.整数 s2 := "100" i2, err := strconv.ParseInt(s2, 10, 64) if err != nil { fmt.Println(err) return } fmt.Printf("%T, %d\n", i2, i2) ss2 := strconv.FormatInt(i2, 10) fmt.Printf("%T, %s\n", ss2, ss2) //itoa() atoi() i3, err := strconv.Atoi("-42") fmt.Printf("%T, %d\n", i3, i3) ss3 := strconv.Itoa(-42) fmt.Printf("%T, %s\n", ss3, ss3) }
七、函数
1.函数的定义
函数是执行特定任务的代码块
go 语言至少有一个main函数
语法格式
package main import "fmt" func main() { /* 函数: function 一、概念: 具有特定功能的代码, 可以被多次调用执行 二、意义: 1.可以避免重复的代码 2.增强程序的扩展性 三、使用: step1: 函数的定义,也叫声明 step2: 函数的调用,就是执行函数中代码的过程 四、语法: 1.定义函数的语法 func funcName(parametername type1, parametername type2)(output1 type1, output2 type2) { //这里是处理逻辑代码 //返回多个值 return value1, value2 } A:func, 定义函数的关键字 B:funcName, 函数的名字 C:(), 函数的标志 D:参数列表: 形式参数用于接收外部传入函数中的数据 E:返回值列表:函数执行后返回给调用处的结果 2.调用函数的语法 函数名(实际参数) 函数的调用处,就是函数调用的位置。 3.注意事项 A:函数必须先定义,再调用,如果不定义: undefined: 定义了函数,没有调用,那么函数就失去了意义 B:函数名不能冲突 C:main(), 是一个特殊的函数,作为程序的入口,由系统自动调用 而其他函数,程序中通过函数名来调用。 */ getSum() fmt.Println("hello world") } // 定义一个函数:用于求1-10的和 func getSum() { sum := 0 for i := 1; i <= 10; i++ { sum += i } fmt.Printf("1-10的和是:%d\n", sum) }
2.函数的参数
package main import "fmt" func main() { /* 函数的参数 形式参数: 也叫形参。函数定义的时候,用于接收外部传入的数据的变量。 函数中,某些变量的数值无法确定,需要由外部传入数据。 实际参数:也叫实参。函数调用的时候,给形参赋值的实际的数据 函数调用: 1.函数名:声明的函数名和调用的函数名要统一 2.实参必须严格匹配形参:顺序,个数,类型,一一对应 */ //1.求 1-10 的和 getSum(10) //2.求 1-20 的和 getSum(20) //3.求 1-100 的和 getSum(100) //4.求2个整数的和 getAdd(3, 8) getAdd2(5, 9) fun1(4.2, 6.5, "add") } // 定义一个函数:用于求1-10的和 func getSum(n int) { sum := 0 for i := 1; i <= n; i++ { sum += i } fmt.Printf("1-%d的和是: %d\n", n, sum) } func getAdd(a int, b int) { sum := a + b fmt.Printf("%d + %d = %d\n", a, b, sum) } func getAdd2(a, b int) { //参数的类型一致,可以简写在一起 fmt.Printf("%d + %d = %d\n", a, b, a+b) } func fun1(a, b float64, c string) { fmt.Printf("a:%.2f, b:%.2f, c:%s\n", a, b, c) }
3.可变参数
package main import "fmt" func main() { /* 可变参数: 概念:一个函数的参数的类型确定,但是个数不确定,就可以使用可变参数. 语法: 参数名...参数的类型 对于函数,可变参数相当于一个切片。 调用函数的时候,可以传入0 - 多个参数 Println(), Printf(), Print() append() 注意事项: A: 如果一个函数的参数是可变参数,同时还有其他的参数,可变参数要放在 参数列表的最后。 B:一个函数的参数列表中最多只能有一个可变参数。 */ //1.求和 getSum3() getSum3(1, 2, 3, 4, 5) getSum3(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) //2.切片 s1 := []int{1, 2, 3, 4, 5} getSum3(s1...) } func getSum3(nums ...int) { fmt.Print("%T\n", nums) sum := 0 for i := 0; i < len(nums); i++ { sum += nums[i] } fmt.Println() fmt.Println("总和是:", sum) } func func1(s1, s2 string, nums ...float64) { }
4.参数传递
package main import "fmt" func main() { /* 数据类型: 一: 按照数据类型: 基本类型: int, float, string, bool 复合数据类型: array, slice, map, struct, interface ... 二: 按照数据的存储特点来分: 值类型的数据:操作的是数值本身。 int, float64, bool, string, array 引用类型的数据:操作的是数据的地址 slice, map, chan 参数传递: A:值传递:传递的是数据的副本。修改数据,对于原始的数据没有影响 值类型的数据,默认都是值传递:基础类型,array, struct B:引用传递:传递的是数据的地址。导致多个变量指向同一块内存。 引用类型的数据,默认都是引用传递: slice, map, chan */ arr1 := [4]int{1, 2, 3, 4} fmt.Println("函数调用前, 数组的数据:", arr1) //[1 2 3 4] fun(arr1) fmt.Println("函数调用后, 数组的数据:", arr1) //[1 2 3 4] fmt.Println("-------------------------------------------------") s1 := []int{1, 2, 3, 4} fmt.Println("函数调用前, 切片的数据:", s1) //[1 2 3 4] fun2(s1) fmt.Println("函数调用后, 切片的数据:", s1) //[200 2 3 4] } func fun2(s2 []int) { fmt.Println("函数中, 切片的数据:", s2) //[1 2 3 4] s2[0] = 200 fmt.Println("函数中, 切片的数据更改后:", s2) //[200 2 3 4] } func fun(arr2 [4]int) { fmt.Println("函数中,数组的数组:", arr2) //[1 2 3 4] arr2[0] = 100 fmt.Println("函数中,数组的数据修改后:", arr2) //[100 2 3 4] }
5.返回值
package main import "fmt" func main() { /* 函数的返回值: 一个函数的执行结果,返回给函数的调用处。执行结果就叫做函数的返回值。 return语句: 一个定义上有返回值,那么函数中必须使用return语句,将结果返回给调用处。 函数返回的结果,必须和函数定义的一致:类型,个数,顺序 1.将函数的结果返回给调用处 2.同进结束了该函数的执行 空白标识符,专门用于舍弃数据: */ //1.设计一个函数,用于求 1 - 10 的各,将结果在主函数中打印输出 res := getSum2() fmt.Println("1-10的和:", res) res2 := getSum4() fmt.Println("求和结果:", res2) //length, wid := float64(5.0), float64(3.0) //fmt.Printf("%T, %T:", length, wid) res5, res6 := rectangle(5, 7) fmt.Println("周长:", res5, "面积:", res6) res7, res8 := rectangle2(5, 7) fmt.Println("周长:", res7, "面积:", res8) res9, _ := rectangle2(5, 7) fmt.Println("周长:", res9) } //函数,用于求矩形的周长和面积 func rectangle(length, wid float64) (float64, float64) { perimeter := (length + wid) * 2 area := length * wid return perimeter, area } func rectangle2(len, wid float64) (peri float64, area float64) { peri = (len + wid) * 2 area = len * wid return } func fun3() (float64, float64, string) { return 4, 5.2, "hello" } func getSum2() int { sum := 0 for i := 1; i <= 10; i++ { sum += i } fmt.Println("1-10的和:", sum) return sum } func getSum4() (sum int) { //定义函数时,指明要返回的数据是哪一个 fmt.Println("函数中:", sum) // 函数中: 0 for i := 0; i <= 100; i++ { sum += i } return }
6.return语句
package main import "fmt" func main() { /* return语名: 词义“返回” A:一个函数有返回值,那么使用return将返回值返回给调用处 B: 同时意味着结束了函数的执行 注意点: 1.一个函数定义了返回值,必须使用return语句将结果返回给调用处。return后的数据必须和函数定义的一致: 个数,类型,顺序 2.可以使用_,来舍弃多余的返回值 3.如果一个函数定义了有返回值,那么函数中有分支,循环,那么要保证,无论执行了哪个分支,都要有return语句被执行到 4.如果一个函数没有定义返回值,那么函数中也可以使用return,专门用于函数的结束执行。 */ a, b, c := func2() fmt.Println(a, b, c) _, _, d := func2() fmt.Println(d) fmt.Println(func3(-30)) func4() } func func2() (int, float64, string) { return 0, 5.2, "hello" } func func3(age int) int { if age >= 0 { return age } else { fmt.Println("年龄不能为负...") return 0 } } func func4() { for i := 0; i < 10; i++ { if i == 5 { return } fmt.Println(i) } fmt.Println("func(3)........") }
7.变量的作用域
package main import "fmt" //全局变量的定义 //num3 := 1000 不支持简短定义的写法 var num3 = 1000 func main() { /* 作用域: 变量可以使用的范围 局部变量: 函数内部定义的变量,就叫做局部变量 变量在哪里定义,就只能在哪个范围使用,超出这个范围,我们认为变量就被销毁了。 全局变量: 函数外部定义的变量,变叫做全局变量 所有的函数都可以使用,而且共享这一份数据 */ //定义在main函数中,所以n的作用域就是main函数的范围内 n := 10 fmt.Println(n) //10 if a := 1; a <= 10 { fmt.Println(a) //1 fmt.Println(n) //10 } //fmt.Println(a) 不能访问,出了作用域 fmt.Println(n) //10 if b := 1; b <= 10 { n := 20 fmt.Println(b) //1 fmt.Println(n) //20 } fmt.Println(n) //10 func5() func6() fmt.Println("main中访问全局变量:", num3) } func func5() { //fmt.Println(n) num1 := 100 fmt.Println("func5()函数中:num1: ", num1) num3 = 2000 fmt.Println("func5()函数,访问全局变量:", num3) } func func6() { //fmt.Println(n) num1 := 200 fmt.Println("func6()函数中:num1: ", num1) fmt.Println("func6()函数,访问全局变量:", num3) }
8.递归
package main import "fmt" func main() { /* 递归函数(recursion):一个函数自己调用自己,就叫做递归函数。 递归函数要有一个出品,逐淅的向出口靠近 */ //1.求1-5的和 sum := getSum5(5) fmt.Println(sum) //2.fibonacci数列 /* 1 2 3 4 5 6 7 8 9 10 11 12 1 1 2 3 5 8 13 21 34 55 89 144 */ res := getFibonacci(12) fmt.Println(res) } func getFibonacci(n int) int { if n == 1 || n == 2 { return 1 } return getFibonacci(n-1) + getFibonacci(n-2) } func getSum5(num1 int) int { if num1 == 1 { return 1 } return getSum5(num1-1) + num1 }
9.defer语句
package main import "fmt" func main() { /* defer的词义:"延迟","推迟" 在go语言中,使用defer关键字来延迟一个函数或者方法的执行。 1.defer函数或方法:一个函数或方法的执行被延迟了. 2.defer的用法 A:对象.close(), 临时文件的删除 B: go语言中关于异常的处理,使用panic()和recover() panic函数用于引发恐慌,导致程序中断执行 recover函数用于恢复程序的执行,recover()语法上要求必须在defer中执行 3.如果多个defer函数:先延迟的后执行,后延迟的先执行 4.defer函数传递参数的时候: defer函数调用时,就已经传递了参数数据了,只是暂时不执行函数中的代码而已 5.defer函数注意点: 当外围函数中的语句正常执行完毕时,只有其中所有的延迟函数都执行完毕,外围函数才会真正的结束执行。 当执行外围函数中的return语句时,只有其中所有的延迟函数都执行完毕后,外围函数才会真正返回。 当外围函数中的代码引发运行恐慌时,只有其中所有的延迟函数都执行完毕后,该运行时恐慌才会真正被扩展至调用函数。 */ // fun5("hello") // defer fmt.Println("12345") // defer fun5("world") // fmt.Println("王二狗") a := 2 fmt.Println(a) defer fun6(a) a++ fmt.Println("main: ", a) fmt.Println(fun7()) } func fun7() int { fmt.Println("fun7()函数的执行...") defer fun5("哈哈") return 0 } func fun6(a int) { fmt.Println("fun6()函数中的打印a: ", a) } func fun5(s string) { fmt.Println(s) }
10. 函数的类型
package main import "fmt" func main() { /* go语言的数据类型: 复合类型: function 函数的类型: func (参数列表的数据类型) (返回值列表的数据类型) */ a := 10 fmt.Printf("%T\n", a) b := [4]int{1, 2, 3, 4} fmt.Printf("%T\n", b) /* [4]string [6]float64 */ c := []int{1, 2, 3, 4} fmt.Printf("%T\n", c) d := make(map[int]string) fmt.Printf("%T\n", d) /* map[string]string map[string]map[int]string */ fmt.Printf("%T\n", fun11) //func() fmt.Printf("%T\n", fun12) //func(int) int fmt.Printf("%T\n", fun13) //func(float64, int, int) (int, int) fmt.Printf("%T\n", fun14) //func(string, string, int, int) (string, int, float64) } func fun11() {} func fun12(int) int { return 0 } func fun13(a float64, b, c int) (int, int) { return 0, 0 } func fun14(a, b string, c, d int) (string, int, float64) { return "", 0, 0 }
11.函数的运算
package main import "fmt" func main() { /* Go语言的数据类型 数值类型:整数,浮点 进行运算操作,加减乘除,打印 字符串: 可以获取单个字符,截取子串,遍历,strings包下的函数操作。 数组,切片,map. 存储数据,修改数据,获取数据,遍历数据... 函数: 名称 加 (),进行调用 注意点: 函数做为一种复合数据类型,可以看做是一种特殊的变量。 函数名():将函数进行调用,函数中的代码会全部执行,然后将return的结果返回给调用处 函数名: 指向函数体的内存地址 */ //1.整型 a := 10 //运算 a += 5 fmt.Println("a: ", a) //2.数组, 切片, map, 容器 b := [4]int{1, 2, 3, 4} b[0] = 100 for i := 0; i < len(b); i++ { fmt.Print(b[i], " ") } fmt.Println() //3.函数做一个变量 fmt.Println("%T\n", func11) fmt.Println(func11) //func11() func11 0xa3e400,看做函数名对应的函数体的地址 //4.直接定义一个函数类的变量 var c func(int, int) fmt.Println(c) //<nil> 空 c = func11 //将fun1的值(函数体的地址)赋值给c fmt.Println(c) func11(10, 20) c(100, 200) //c也是函数类型的,加小括号也可以被调用 res1 := func12 //将func12的值(函数的地址)赋值给res1, res1和func12指向同一个函数体 res2 := func12(1, 2) //将func12函数进行调用,将函数的执行结果赋值给res2,相当于:a+b fmt.Println(res1) fmt.Println(res2) res1(10, 30) fmt.Println(res1) res2() //invalid operation: cannot call non-function res2 (variable of type int) } func func11(a, b int) { fmt.Printf("a:%d, b:%d\n", a, b) } func func12(a, b int) int { return a + b }
12.匿名函数
package main import "fmt" func main() { /* 匿名:没有名字 匿名函数:没有名字的函数 定义一个匿名函数,直接进行调用。通常只能使用一次,也可以使用匿名函数赋值给某个函数变量,那么就可以调用多次了。 匿名函数: Go语言是支持函数式编程 1.将匿名函数作为另一个函数的参数,回调函数 2.将匿名函数作为另一个函数的返回值,可以形成闭包结构 */ fun111() fun111() fun2 := fun111 fun2() //匿名函数 func() { fmt.Println("这里是一个匿名函数....") }() fun3 := func() { fmt.Println("这里是另一个匿名函数....") } fun3() fun3() //定义带返回值的匿名函数 res1 := func(a, b int) int { return a + b }(10, 20) //匿名函数调用了,将执行结果给res1 fmt.Println(res1) res2 := func(a, b int) int { return a + b } //将匿名函数的值,赋值给res2 fmt.Println(res2) fmt.Println(res2(150, 289)) } func fun111() { fmt.Println("我是fun111()函数...") }
13.回调函数
package main import "fmt" func main() { /* 高阶函数: 根据go语言的数据类型的特点,可以将一个函数作为另一个函数的参数。 fun1(), fun2() 将func1函数作为了fun2这个函数的参数。 fun2函数:就叫高阶函数 接收了一个函数作为参数的函数,高阶函数 fun1函数:回调函数 作为另一个函数的参数的函数,叫做回调函数。 */ //设计一个函数,用于求两个整数的加减乘除运算 fmt.Printf("%T\n", add) //func(int, int) int fmt.Printf("%T\n", oper) //func(int, int, func(int, int) int) int res1 := add(4, 6) fmt.Println(res1) res2 := oper(10, 20, add) fmt.Println(res2) res3 := oper(50, 20, sub) fmt.Println(res3) fun1 := func(a, b int) int { return a * b } res4 := oper(5, 6, fun1) fmt.Println(res4) res5 := oper(8, 4, func(a, b int) int { if b == 0 { fmt.Println("除数不能为零") return 0 } return a / b }) fmt.Println(res5) } func add(a, b int) int { return a + b } func sub(a, b int) int { return a - b } func oper(a, b int, fun func(int, int) int) int { fmt.Println(a, b, fun) res := fun(a, b) return res }
14.闭包
package main import "fmt" func main() { /* go语言支持函数式编程: 支持将一个函数作为另一个函数的参数。 也支持将一个函数作为另一个函数的返回值 闭包(closure): 一个外层函数中,有内层函数,该内层函数中,会操作外层函数的局部变量(外层函数中的参数,或者外层函数中直接定义的变量), 并且该外层函数的返回值就是这个内层函数。 这个内层函数和外层函数的局部变量,统称为闭包结构 局部变量的生命周期会发生改变, 正常的局部变量随着函数调用而创建,随着函数的结束而销毁。 但是闭包结构中的外层函数的局部变量并不会随着外层函数的结束而销毁,因为内层函数还要继续使用。 */ res1 := increment() //res1 = fun fmt.Printf("%T\n", res1) fmt.Println(res1) v1 := res1() fmt.Println(v1) v2 := res1() fmt.Println(v2) fmt.Println(res1()) fmt.Println(res1()) fmt.Println(res1()) res2 := increment() fmt.Println(res2) v3 := res2() fmt.Println(v3) fmt.Println(res2()) fmt.Println(res1()) } func increment() func() int { //1.定义了一个局部变量 i := 0 //2.定义了一个匿名函数,给变量自增并返回 fun := func() int { //内层函数 i++ return i } //3.返回该匿名函数 return fun }
八、指针
1.指针的定义
package main import "fmt" func main() { /* 指针:pointer 存储了另一个变量的内存地址的变量 */ //1.定义一个int类型的变量 a := 10 fmt.Println("a的数值是: ", a) fmt.Printf("%T\n", a) fmt.Printf("a的地址是:%p\n", &a) //0xc00001a088 //2.创建一个指针变量,用于存储变量a的地址 var p1 *int fmt.Println(p1) //<nil> p1 = &a //p1指向了a的内存地址 fmt.Println("p1的数值: ", p1) //p1中存储的是a的地址 fmt.Printf("p1自己的地址:%p\n", &p1) fmt.Println("p1的数值,是a的地址,该地址存储的数据: ", *p1) //获取指针指向的变量的数值 //3.操作变量,更改数值,并不会改变地址 a = 100 fmt.Println(a) fmt.Printf("%%p\n", &a) //4.通过指针,改变变量的的数值 *p1 = 200 fmt.Println(a) //5.指针的指针 var p2 **int fmt.Println(p2) p2 = &p1 fmt.Printf("%T, %T, %T\n", a, p1, p2) //int, * int, **int fmt.Println("p2的数值: ", p2) //p1的地址 fmt.Printf("p2自己的地址:%p\n", &p2) fmt.Println("p2中存储的地址,对应的数值,就是p1的地址,对应的数据: ", *p2) fmt.Println("p2中存储的地址,对应的数值,再获取对应的数据: ", **p2) }
2.数组指针和指针数组
package main import "fmt" func main() { /* 数组指针: 首先是一个指针,一个数组的地址 *[4]Type 指针数组:首先是一个数组,存储的数据类型是指针 [4]*Type *[5]float64, 指针,一个存储了5个浮点类型数据的数组的指针 *[3]string, 指针,数组的指针,存储了3个字符串 [3]*string, 数组,存储了3个字符串的指针地址的数组 [5]*float64, 数组,存储屯5个浮点数据的地址的数组 *[5]*float64, 指针,一个数组的指针,存储了5个float类型的数据的指针地址的数组的指针 *[3]*string, 指针,存储了3个字符串的指针地址的数组的指针 **[4]string, 指针,存储了4个字符串数据的数组的指针的指针 **[4]*string,指针,存储了4个字符串的指针地址的数组,的指针的指针 */ //1.创建一个普通的数组 arr1 := [4]int{1, 2, 3, 4} fmt.Println(arr1) //2.创建一个指针,存储该数组的地址->数组指针 var p1 *[4]int p1 = &arr1 fmt.Println(p1) //&[1 2 3 4] fmt.Printf("%p\n", p1) //数组arr1的地址 fmt.Printf("%p\n", &p1) //p1指针的地址 //3.根据数组指针,操作数组 (*p1)[0] = 100 fmt.Println(arr1) p1[0] = 200 //简化写法 fmt.Println(arr1) //4.指针数组 a := 1 b := 2 c := 3 d := 4 arr2 := [4]int{a, b, c, d} arr3 := [4]*int{&a, &b, &c, &d} fmt.Println(arr2) fmt.Println(arr3) arr2[0] = 100 fmt.Println(arr2) fmt.Println(a) *arr3[0] = 200 fmt.Println(arr3) fmt.Println(a) b = 1000 fmt.Println(arr2) fmt.Println(arr3) for i := 0; i < len(arr3); i++ { fmt.Println(*arr3[i]) } }
3.函数指针和指针函数
package main import "fmt" func main() { /* 函数指针:一个指针,指向了一个函数的指针。 因为go语言中,function,默认看作一个指针,没有*. slice, map,function 指针函数:一个函数,该函数的返回值是一个指针 */ var a func() a = fun1111 a() arr1 := func2222() fmt.Printf("arr1的类型: %T, 地址:%p, 数值: %v\n", arr1, &arr1, arr1) arr2 := func3333() fmt.Printf("arr2的类型: %T, 地址:%p, 数值: %v\n", arr2, &arr2, arr2) fmt.Printf("arr2指针中存储的数组的地址:%p\n", arr2) } func func3333() *[4]int { arr := [4]int{5, 6, 7, 8} fmt.Printf("函数中arr的地址: %p\n", &arr) return &arr } func func2222() [4]int { //普通函数 arr := [4]int{1, 2, 3, 4} fmt.Printf("arr的类型: %T, 地址:%p, 数值: %v\n", arr, &arr, arr) return arr } func fun1111() { fmt.Println("fun1111..........") }
4.指针作为参数
package main import "fmt" func main() { /* 指针作为参数 参数的传递:值传递,引用传递 */ a := 10 fmt.Println("fun1000()函数调用前, a:", a) fun1000(a) fmt.Println("fun1000()函数调用后, a:", a) fun2000(&a) fmt.Println("fun2000()函数调用后, a:", a) arr1 := [4]int{1, 2, 3, 4} fmt.Println("fun3000{}函数调用前:", arr1) fun3000(arr1) fmt.Println("fun3000{}函数调用后:", arr1) fun4000(&arr1) fmt.Println("fun4000()函数调用后:", arr1) s1 := []int{1,2,3,4,5} //本身为指针 } func fun4000(p2 *[4]int) { //引用传递 fmt.Println("fun4000()函数中的数组指针:", p2) p2[0] = 200 fmt.Println("fun4000()函数中的数组指针", p2) } func fun3000(arr2 [4]int) { //值传递 fmt.Println("fun3000()函数中数组的:", arr2) arr2[0] = 100 fmt.Println("fun3000()函数中修改数组:", arr2) } func fun1000(num int) { //值传递: num=a=10 fmt.Println("fun1000函数中,num的值:", num) num = 100 fmt.Println("fun1000函数中修改num:", num) } func fun2000(p1 *int) { //传递的是a的地址,就是引用传递。 fmt.Println("fun2000()函数中,p1: ", *p1) *p1 = 200 fmt.Println("fun2000()函数中,修改p1: ", *p1) }
九、结构体
1.结构构定义
package main import "fmt" func main() { /* 结构体:是由一系列具有相同类型或不同类型的数据构成的数据集全 结构体成员 是由一系列的成员变量构成,这些成员变量也被称为”字段“ */ //1.方法一 var p1 Person fmt.Println(p1) p1.name = "王二狗" p1.age = 30 p1.sex = "男" p1.address = "北京市" fmt.Printf("姓名: %s, 年龄: %d, 性别:%s, 地址:%s\n", p1.name, p1.age, p1.sex, p1.address) //2.方法二 p2 := Person{} p2.name = "Ruby" p2.age = 28 p2.sex = "女" p2.address = "上海市" fmt.Printf("姓名: %s, 年龄: %d, 性别:%s, 地址:%s\n", p2.name, p2.age, p2.sex, p2.address) //3.方法三 p3 := Person{name: "如花", age: 20, sex: "女", address: "杭州市"} fmt.Println(p3) p4 := Person{ name: "老王", age: 40, sex: "男", address: "武汉市"} fmt.Println(p4) //4.方法四 p5 := Person{"李小花", 36, "女", "成都"} fmt.Println(p5) } // 1.定义结构体 type Person struct { name string age int sex string address string }
2.结构体指针
package main import "fmt" func main() { /* 结构体是值类型: make用于内建类型(map, slice和channel)的内存分配。 new用于各种类型的内存分配, 内建函数new本质上说跟其它语言中的同名函数功能一样: new(T)分配了零值填充的T类型的内存空间, 并且返回地址,即一个*T类型的值。用go的术语说,它返回了一个指针,指向新分配的类型T的零值。 有一点非常重要: new 返回指针 通过指针: new(), 不是nil, 空指针 指向了新分配的类型的内存空间,里面存储的零值 */ //1.结构体是值类型 p1 := Person{"王二狗", 30, "男", "北京市"} fmt.Println(p1) fmt.Printf("%p, %T\n", &p1, p1) p2 := p1 fmt.Println(p2) fmt.Printf("%p, %T\n", &p2, p2) p2.name = "李二花" fmt.Println(p1) fmt.Println(p2) //2.定义结构体指针 var pp1 *Person pp1 = &p1 fmt.Println(pp1) fmt.Printf("%p, %T\n", pp1, pp1) fmt.Println(*pp1) (*pp1).name = "李四" pp1.name = "王麻子" fmt.Println(pp1) fmt.Println(p1) //使用内置函数new(),go语言中专门用于创建某种类型的指针的函数 pp2 := new(Person) fmt.Printf("%T\n", pp2) fmt.Println(pp2) //(*pp2).name pp2.name = "Jerry" pp2.age = 20 pp2.sex = "男" pp2.address = "上海市" fmt.Println(pp2) pp3 := new(Person) fmt.Println(pp3) fmt.Println(*pp3) }
3.结构体的匿名字段
package main import "fmt" func main() { /* 匿名结构体和匿名字段: 匿名结构体:没有名字的结构体 在创建匿名结构体时,同时创建对象 变量名 := struct { 定义字段Field }{ 字段进行赋值 } 匿名字段:一个结构体的字段没有字段名 匿名函数: */ s1 := Student{name: "张三", age: 18} fmt.Println(s1.name, s1.age) //匿名函数 f1 := func() { fmt.Println("Hello world...") } f1() //匿名结构体 s2 := struct { name string age int }{ name: "李四", age: 19, } fmt.Println(s2.name, s2.age) //w1 := Worker{name: "王麻子", age: 30} //fmt.Println(w1.name, w1.age) w2 := Worker{"李小花", 32} fmt.Println(w2) fmt.Println(w2.string) fmt.Println(w2.int) } type Worker struct { //name string //age int string //匿名字段 int //匿名字段,默认使用数据类型作为名字,那么匿名字段的类型不能重复,否则会冲突 } type Student struct { name string age int }
4.结构体嵌套
package main import "fmt" func main() { /* 结构体嵌套:一个结构体中的字段,是另一个结构体类型. has a */ b1 := Book{} b1.bookName = "西游记" b1.price = 45.8 s1 := Pupil{} s1.name = "李三" s1.age = 18 s1.book = b1 //值传递 fmt.Println(b1) fmt.Println(s1) fmt.Printf("学生姓名:%s, 学生年龄:%d, 看的书是: <<%s>>, 书的价格是: %.2f\n", s1.name, s1.age, s1.book.bookName, s1.book.price) //验证值传递 s1.book.bookName = "红楼梦" fmt.Println(s1) fmt.Println(b1) s2 := Pupil{name: "李小花", age: 19, book: Book{bookName: "go从入门到放弃", price: 89.7}} fmt.Println(s2.name, s2.age) fmt.Println("\t", s2.book.bookName, s2.book.price) s3 := Pupil{ name: "Jerry", age: 17, book: Book{ bookName: "一千万个为什么", price: 55.9, }, } fmt.Println(s3) fmt.Println(s3.name, s3.age) fmt.Println("\t", s3.book.bookName, s3.book.price) b4 := Book{bookName: "呼啸山庄", price: 76.9} s4 := Pupil2{name: "Ruby", age: 18, book: &b4} fmt.Println(b4) fmt.Println(s4) fmt.Println("\t", s4.book) s4.book.bookName = "雾都孤儿" fmt.Println(b4) fmt.Println(s4) fmt.Println("\t", s4.book) } // 1.定义一个书的结构体 type Book struct { bookName string price float64 } // 2.定义学生的结构体 type Pupil struct { name string age int book Book } type Pupil2 struct { name string age int book *Book //book的地址 }
5.go语言的oop
package main import "fmt" func main() { /* 面向对象:OOP 1.模拟继承性: is -a type A struct { field } type B struct { A //匿名字段 } 2.模拟聚合关系: has -a type C struct { field } type D struct { c D //聚合关系 } */ //1.创建父类的对象 p1 := Person{name: "张三", age: 30} fmt.Println(p1) fmt.Println(p1.name, p1.age) //2.创建子类的对象 s1 := Student1{Person{"李四", 17, "男", "育才路"}, "西安三中"} fmt.Println(s1) s2 := Student1{Person: Person{name: "马六", age: 18, sex: "女", address: "张飞路"}, school: "西安五中"} fmt.Println(s2) var s3 Student1 s3.Person.name = "羊八" s3.Person.age = 19 s3.school = "西安高新学校" fmt.Println(s3) //简写,相当于把字段从父类中提升到子类, s3.name = "Ruby" s3.sex = "woman" fmt.Println(s3) fmt.Println(s1.name, s1.age, s1.school) fmt.Println(s2.name, s2.age, s2.school) fmt.Println(s3.name, s3.age, s3.school) } //1.定义父类 type Person struct { name string age int } // 2.定义子类 type Student1 struct { Person //模拟继承结构 school string //子类的新增属性 }
6.方法
package main import "fmt" func main() { /* 方法:method 一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。 所有给定类型的方法属于该类型的方法集 语法: func (接收者) 方法名(参数列表) (返回值列表) { } 总结:method, 同函数类似,区别需要有接受者。(也就是调用者) 方法的作用域同变量的作用域 对比函数: A:意义 方法:某个类另的行为功能,需要指定的接受者调用 函数:一段独立功能的代码,可以直接调用 B:语法 方法:方法名可以相同,只要接受者不同 函数:命名不能冲突 */ w1 := Worker1{name: "李翠花", age: 32, sex: "女"} w1.work() w2 := &Worker1{name: "Ruby", age: 30, sex: "女"} fmt.Printf("%T\n", w2) w2.work() w2.rest() w1.rest() w2.printInfo() c1 := Cat{color: "yellow", age: 4} c1.printInfo() } // 1.1定义一个工人结构体 type Worker1 struct { //字段 name string age int sex string } type Cat struct { color string age int } // 2.定义行为方法 func (w Worker1) work() { //w = w1 fmt.Println(w.name, "在工作.....") } func (p *Worker1) rest() { //p = w2, p = w1的地址 //p.name 是 *p.name简写 fmt.Println(p.name, "在休息.....") } func (p *Worker1) printInfo() { fmt.Printf("工人姓名:%s, 工人年龄: %d, 工人性别: %s\n", p.name, p.age, p.sex) } func (p *Cat) printInfo() { fmt.Printf("猫咪的颜色:%s, 年龄: %d\n", p.color, p.age) }
7.继承中的方法
package main import "fmt" func main() { /* OOP中的继承性: 如果两个类(class)存在继承关系,其中一个是子类,另一个作为父类,那么: 1.子类可以直接访问父类的属性和方法 2.子类可以新增自己的属性和方法 3.子类可以重写父类的方法(override) Go语言的结构体嵌套: 1.模拟继承性: is -a type A struct { field } type B struct { A //匿名字段 } 2.模拟聚合关系: has -a type C struct { field } type D struct { c C //聚合函数 } */ //1.创建Person类型 p1 := Human{name: "李五", age: 30} fmt.Println(p1.name, p1.age) p1.eat() //父类对象,访问父类的方法 //2.创建Student类型 s1 := Student2{Human{"Ruby", 18}, "清华大学"} fmt.Println(s1.name) //s12.Human.name fmt.Println(s1.age) //子类对象,可以直接访问父类的字符(其实就是提升字段) fmt.Println(s1.school) //子类对象,访问自己新曾的字段属性 s1.eat() //子类对象,访问父类的方法 s1.study() //子类对象,访问自己新增的方法 s1.eat() //如果存在方法的重写,子类对象访问重写的方法 } // 1.定义一个“父类” type Human struct { name string age int } // 2.定义一个“父类” type Student2 struct { Human //结构体嵌套,模拟继承性 school string } // 3.方法 func (h Human) eat() { fmt.Println("父类的方法,吃窝窝头....") } func (s Student2) study() { fmt.Println("子类新增的方法,学生学习啦...") } func (s Student2) eat() { fmt.Println("子类重写的方法,吃炸鸡喝可乐") }
十、接口
1.接口定义
在面向对象中,接口一般是定义一组对象的行为动作。
在go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。
2.接口的定义语法
package main import "fmt" func main() { /* 接口:interface 在Go中,接口是一组方法签名 当某个类型为这个接口中的所有方法提供了方法的实现,它被称为实现接口。 go语言中,接口和类型的实现关系,是非侵入式 // 其他语言中,要显示的定义 class Mouse implements USB{} 1.当需要接口类型的对象时,可以使用任意实现类对象代替 2.接口对象不能访问实现类中的属性 多态:一个事物的多种形态 go语言通过接品模拟多态 就一个接口的实现 1.看成实现本身的类型,能够访问实现类中的属性和方法 2.看成是对应的接口类型,那就只能够访问接口中的方法 接口的用法: 1.一个函数如果接受接口类型作为参数,那么实际上可以传入该接口的任意实现类型对象作为参数。 2. 定义一个类型为接口类型,实际上可以赋值为任意实现类的对象 鸭子类型: */ //1.创建Mouse类型 m1 := Mouse{"罗技小红"} fmt.Println(m1.name) //2.创建FlashDisk f1 := FlashDisk{"闪迪64G"} testInterface(m1) testInterface(f1) var usb USB usb = m1 usb.start() usb.end() //fmt.Println(usb.name) f1.deleteData() var arr [3]USB arr[0] = m1 arr[1] = f1 fmt.Println(arr) } // 1.定义接口 type USB interface { start() //USB设备开始工作 end() //USB设备结束工作 } // 2.实现类 type Mouse struct { name string } type FlashDisk struct { name string } func (m Mouse) start() { fmt.Println(m.name, "鼠标,准务就绪,可以开始工作了,点点点.... ") } func (m Mouse) end() { fmt.Println("结束工作,可以案例退出...") } func (f FlashDisk) start() { fmt.Println(f.name, "准备开始工作,可以进行数据的存储....") } func (f FlashDisk) end() { fmt.Println(f.name, "可以弹出,。。。") } // 3.调试方法 func testInterface(usb USB) { usb.start() usb.end() } func (f FlashDisk) deleteData() { fmt.Println(f.name, "U盘删除数据....") }
3.空接口
package main import "fmt" func main() { /* 空接口(interface()) 不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型的数值。 fmt包下的Print系列函数: func Println(a ...any) (n int, err error) func Printf(format string, a ...any) (n int, err error) func Println(a ...any) (n int, err error) */ var a1 A = Cat1{"花猫"} var a2 A = Human1{"李小菲", 28} var a3 A = "haha" var a4 A = 100 fmt.Println(a1) fmt.Println(a2) fmt.Println(a3) fmt.Println(a4) test1(a1) test1(a2) test1(3.14) test1("Ruby") test("haha") test(a2) //map, key字符串, value任意类型 map1 := make(map[string]interface{}) map1["name"] = "李小花" map1["age"] = 30 map1["friend"] = Human1{"Jerry", 18} fmt.Println(map1) //切片,存储任意类型的数据 slice1 := make([]interface{}, 0, 10) slice1 = append(slice1, a1, a2, a3, a4, 100, "abc") fmt.Println(slice1) test3(slice1) } func test3(slice2 []interface{}) { for i := 0; i < len(slice2); i++ { fmt.Printf("第%d个数据:%v\n", i+1, slice2[i]) } } // 接口A是空接口,理解为代表了任意类型 func test1(a A) { fmt.Println(a) } func test(a interface{}) { fmt.Println("---------->", a) } // 空接口 type A interface{} type Cat1 struct { color string } type Human1 struct { name string age int }
4.接口嵌套
package main import "fmt" func main() { /* 接口嵌套 */ var cat Cat2 cat.test4() cat.test5() cat.test6() fmt.Println("--------------------------------------") var a1 B = cat a1.test4() fmt.Println("--------------------------------------") var b1 D = cat b1.test5() fmt.Println("--------------------------------------") var c1 E = cat c1.test4() c1.test5() c1.test6() } type B interface { test4() } type D interface { test5() } type E interface { B D test6() } type Cat2 struct { //如果想实现接口C,那不止要实现接口C的方法,还要实现接口A,B中的方法 } func (c Cat2) test4() { fmt.Println("test4()........") } func (c Cat2) test5() { fmt.Println("test5()........") } func (c Cat2) test6() { fmt.Println("test6()........") }
5.接口断言
package main import ( "fmt" "math" ) func main() { /* 接口断言 方式一: 1. instance := 接口对象.(实际类型) //不安全,会panic() 2. instance, ok := 接口对象.(实际类型) //安全 方式二:switch switch instance := 接口对象.(type) { case 实际类型1: .... case 实际类型2: .... .... } */ var t1 Triangle = Triangle{3, 4, 5} fmt.Println(t1.peri()) fmt.Println(t1.area()) fmt.Println(t1.a, t1.b, t1.c) var c1 Circle = Circle{4} fmt.Println(c1.peri()) fmt.Println(c1.area()) fmt.Println(c1.radius) var s1 Shape s1 = t1 fmt.Println(s1.peri()) fmt.Println(s1.area()) var s2 Shape s2 = c1 fmt.Println(s2.peri()) fmt.Println(s2.area()) testShape(t1) testShape(c1) testShape(s1) getType(t1) getType(c1) getType(s1) //getType(100) var t2 *Triangle = &Triangle{3, 4, 5} fmt.Printf("t2:%T, %p,%p\n", t2, &t2, t2) getType(t2) getType2(t2) } func getType2(s Shape) { switch ins := s.(type) { case Triangle: fmt.Println("三角形。。。。", ins.a, ins.b, ins.c) case Circle: fmt.Println("圆形。。。。。", ins.radius) case *Triangle: fmt.Println("三角形结构体指针:.......", ins.a, ins.b, ins.c) } } func getType(s Shape) { //断言 if ins, ok := s.(Triangle); ok { fmt.Println("是三角形,三边是:", ins.a, ins.b, ins.c) } else if ins, ok := s.(Circle); ok { fmt.Println("是圆形,半径是: ", ins.radius) } else if ins, ok := s.(*Triangle); ok { //传值 fmt.Printf("ins:%T, %p,%p\n", ins, &ins, ins) fmt.Printf("s:%T, %p, %p\n", s, &s, s) } else { fmt.Println("王二狗想你了") } } func testShape(s Shape) { fmt.Printf("周长:%.2f, 面积:%.2f\n", s.peri(), s.area()) } // 1,定义一个接口 type Shape interface { peri() float64 //形状的周长 area() float64 //形状的面积 } // 2.定义实现类:三角形 type Triangle struct { a, b, c float64 } func (t Triangle) peri() float64 { return t.a + t.b + t.c } func (t Triangle) area() float64 { p := t.peri() / 2 s := math.Sqrt(p * (p - t.a) * (p - t.b) * (p - t.c)) return s } type Circle struct { radius float64 } func (c Circle) peri() float64 { return c.radius * 2 * math.Pi } func (c Circle) area() float64 { return math.Pow(c.radius, 2) * math.Pi }
十一、other
1.type
package main import ( "fmt" "strconv" ) func main() { /* type: 用于类型定义和类型别名 1.类型定义:type 类型名 Type 2.类型别名:type 类型名 = Type */ var i1 myint var i2 = 100 i1 = 200 fmt.Println(i1, i2) var name mystr name = "北大荒晒太阳" var s1 string s1 = "李小花" fmt.Println(name, s1) fmt.Printf("%T, %T, %T, %T\n", i1, i2, name, s1) //main.myint, int, main.mystr, string fmt.Println("------------------------------------") res1 := fun10() fmt.Println(res1(10, 20)) fmt.Println("-----------------------------------------------") var i3 myint2 i3 = 1000 fmt.Println(i3) i3 = i2 fmt.Println(i3) fmt.Printf("%T, %T, %T\n", i1, i2, i3) } // 1.定义一个新的类型 type myint int type mystr string // 2.定义函数类型 type myfun func(int, int) string func fun10() myfun { //fun10()函数的返回值是my_func类型 fun := func(a, b int) string { s := strconv.Itoa(a) + strconv.Itoa(b) return s } return fun } // 3.类型别名 type myint2 = int //不是重新定义的数据类型,只是给int起别名,和int可以通用
package main import ( "fmt" "time" ) type MyDuration time.Duration func (m MyDuration) SimpleSet() { // cannot define new methods on non-local type time.Duration } type Person1 struct { name string } func (p Person1) show() { fmt.Println("Person---->", p.name) } // 类型别名 type People = Person1 func (p People) show2() { //method Person1.show already declared at fmt.Println("People---->", p.name) } type Student11 struct { //嵌入两个结构体 Person1 People } func main() { var s Student11 //s.name = "李三万" //ambiguous selector s.name s.Person1.name = "李三念" s.Person1.show() fmt.Printf("%T, %T\n", s.Person1, s.People) s.People.name = "李小花" s.People.show2() }
2.错误error
package main import ( "fmt" "os" ) func main() { f, err := os.Open("test.txt") if err != nil { //log.Fatal(err) fmt.Println("错误:", err) //open test.txt: The system cannot find the file specified. return } fmt.Println(f.Name(), "打开文件成功....") }
package main import ( "errors" "fmt" ) func main() { /* error: 内置的数据类型,内置的接口 定义方法: Error() string 使用go语言提供好的包: errors包下的函数: New(), 创建一个error对象 fmt包下的Errorf()函数: func Error(format string, a...interface()) error */ //1.创建一个error数据 err1 := errors.New("学习创建中。。。。") fmt.Println(err1) fmt.Printf("%T\n", err1) //*errors.errorString //2.另一个创建error的方法 err2 := fmt.Errorf("错误的信息码: %d", 100) fmt.Println(err2) fmt.Printf("%T\n", err2) fmt.Println("---------------------------------") err3 := checkAge(-30) if err3 != nil { fmt.Println(err3) return } fmt.Println("程序...go on....") } // 设计一个函数:验证年龄是否合法,如果为负数,就返回一个error func checkAge(age int) error { if age < 0 { //返回error对象 //return errors.New("年龄不合法") err := fmt.Errorf("给定的年龄%d是不合法的.", age) return err } fmt.Println("年龄是:", age) return nil }
获取错误信息
package main import ( "fmt" "os" ) func main() { f, err := os.Open("test.txt") if err != nil { //log.Fatal(err) fmt.Println("错误:", err) //open test.txt: The system cannot find the file specified. if ins, ok := err.(*os.PathError); ok { fmt.Println("1.Op:", ins.Op) fmt.Println("2.Op:", ins.Path) fmt.Println("3.Op:", ins.Err) } return } fmt.Println(f.Name(), "打开文件成功....") }
package main import ( "fmt" "net" ) func main() { addr, err := net.LookupHost("www.baidu00001.com") fmt.Println(err) if ins, ok := err.(*net.DNSError); ok { if ins.Timeout() { fmt.Println("操作超时") } else if ins.Temporary() { fmt.Println("临时性错误") } else { fmt.Println("通常错误") } } fmt.Println(addr) }
package main import ( "fmt" "path/filepath" ) func main() { files, err := filepath.Glob("[") if err != nil && err == filepath.ErrBadPattern { fmt.Println(err) return } fmt.Println("matched files", files) }
3.自定义 error
package main import ( "fmt" "math" ) func main() { /* 自定义错误: */ radius := -3.0 area, err := circleArea(radius) if err != nil { fmt.Println(err) if err, ok := err.(*areaError); ok { fmt.Printf("半径是:%.2f\n", err.radius) } return } fmt.Println("圆形的面积是:", area) } // 1.定义一个结构体,表示错误的类型 type areaError struct { msg string radius float64 } // 2.实现error接口,就是实现Error()方法 func (e *areaError) Error() string { return fmt.Sprintf("error: 半径, %.2f, %s", e.radius, e.msg) } func circleArea(radius float64) (float64, error) { if radius < 0 { return 0, &areaError{"半径是非法的", radius} } return math.Pi * radius * radius, nil }
package main import "fmt" func main() { length, width := 3.0, 5.0 area, err := rectArea(length, width) if err != nil { fmt.Println("错误信息:", err) } else { fmt.Println("面积是:", area) } length, width = -3.0, -5.0 area, err = rectArea(length, width) if err != nil { if err, ok := err.(*areaError); ok { if err.lengthNegative() { fmt.Printf("error: 长度,。%.2f, 小于零\n", err.length) } if err.widthNegative() { fmt.Printf("error: 宽度,。%.2f, 小于零\n", err.width) } } fmt.Println("错误信息:", err) } else { fmt.Println("面积是:", area) } } type areaError struct { msg string //错误描述 length float64 //发生错误的时候,矩形的长度 width float64 //发生错误的时候,矩形的宽度 } func (e *areaError) Error() string { return e.msg } func (e *areaError) lengthNegative() bool { return e.length < 0 } func (e *areaError) widthNegative() bool { return e.width < 0 } func rectArea(length, width float64) (float64, error) { msg := "" if length < 0 { msg = "长度小于零" } if width < 0 { msg = "长度小于零" } if width < 0 { if msg == "" { msg = "宽度小于零" } else { msg += ", 宽度也小于零" } } if msg != "" { return 0, &areaError{msg, length, width} } return length * width, nil }
4.panic() 和 recover()
Golang中引入两个内置函数panic和recover来触发和终止异常处理流程,同时引入关键字defer来延迟执行defer后面的函数。
一直等到包含defer语句的函数执行完毕时,延迟函数(defer后的函数)才会被执行,而不管包含defer语句的函数是通过延迟函数。调用者继续传递panic,因此该过程一直在调用栈中重复发生: 函数停止执行,调用延迟执行函数等。如果一路在延迟函数中没有recover函数的调用,则会到达该协程的起点,该协程结束,然后终止其他所有协程,包括主协程.
panic:
1.内建函数
2.假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所有函数F内如果存在要执行的defer函数列表,按照defer的逆序执行,
3.返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表 ,按照defer的逆序执行,这里的defer有点类似try---catch---finally中的finally
4.直到goroutine整个退出,并报告错误。
recover:
1.内建函数
2.用来控制一个goroutine 的panicking行为,捕获panic,从而影响应用的行为
3.一般的调用建议
a).在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
b).可以获取通过panic传递的error
简单来说:go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
package main import "fmt" func main() { /* panic: 词义:"恐慌" recover: "恢复" go语言利用panic(), recover(), 实现程序中的极特殊的异常处理, panic(), 让当前的程序进入恐慌,中断程序的执行 recover(), 让程序恢复,必须在defer函数中执行 */ funA() defer myprint("defer main: 3......") funB() defer myprint("defer main: 4......") fmt.Println("main...over...") } func myprint(s string) { fmt.Println(s) } func funA() { fmt.Println("我是一个函数funA()......") } func funB() { //外围函数 defer func() { if msg := recover(); msg != nil { fmt.Println(msg, "程序恢复了.....") } }() fmt.Println("我是函数funB().....") defer myprint("defer funB():1.......") for i := 1; i <= 10; i++ { fmt.Println("i: ", i) if i == 5 { //让程序中断 panic("funB, 恐慌了...") } } //当外围函数的代码中发生了运行恐慌同,只有其中所有的已经defer的函数全部执行完毕后,该运行恐慌才会真正被扩展至调用处。 defer myprint("defer funB():2.....") }