The "Go" Learning Trip -- 2. Go Basics -- Part1-2
基本数据类型和操作符
1. 文件名&关键字&标识符
1) 所有go源码以.go结尾
2) 标识符以字母或下划线开头,大小写敏感,比如:
3) _是特殊标识符,用来忽略结果
4) 保留关键字
2. Go程序基本结构
- 任何一个代码文件隶属于一个包
- import 关键字,引用其他包:
- golang可执行程序,package main,并且有且只有一个main入口函数
- 包中函数调用:
- 同一个包中函数,直接调用
- 不同包中函数,通过包名+点+函数名进行调用
- 包访问控制规则:
- 大写意味着这个函数/变量是可导出的
- 小写意味着这个函数/变量是私有的,包外部不能访问
# exercise time -- exercise 1
思路: 需要一个迭代,迭代的变量进行递减,并用格式化输出
# 代码如下
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func calcValue(n int) { 8 for item := 0; item <= n; item++ { 9 fmt.Printf("%d + %d = %d\n", item, n-item, n) 10 } 11 } 12 func main() { 13 calcValue(10) 14 }
# exercise time -- exercise 2
# 比较简单直接上码
1 // 新建一个main目录下的mainDemo.go 2 package main 3 4 import ( 5 "fmt" 6 "go_dev/day2/example1/add" 7 ) 8 9 func main() { 10 fmt.Println(add.Name) 11 fmt.Println(add.Age) //这里注意如果用小写的变量名,将提示不可引用 12 } 13 14 // 新建一个add目录下的addPkg.go 15 package add 16 17 // Name is Author 18 var Name = "Loki" 19 20 // Age is Author's age 21 var Age = 16
# exercise time -- exercise 3
# 比较简单 X 2 直接上码
1 // 新建目录main下面的mainDemo.go 2 package main 3 4 import ( 5 "fmt" 6 lokiPkg "go_dev/day2/example3/add" // 这里定义别名 7 ) 8 9 func main() { 10 fmt.Println("result: ", lokiPkg.Name) //这里调用别名 11 fmt.Println("result: ", lokiPkg.Age) 12 } 13 14 // 新建目录add下面的addDemo.go 15 package add 16 17 var Name string = "Loki" 18 var Age int = 16
# exercise time -- exercise 4
# 关于 init() 函数 会优先执行
执行顺序: 先初始化全局变量 --> 调用初始化函数init() --> 执行main()函数
1 //main目录下的mainDemo.go 2 package main 3 4 import ( 5 "fmt" //lokiPkg "go_dev/day2/example3/add" // 这里定义别名 6 "go_dev/day2/example3/add" // 这里定义别名 7 ) 8 9 func main() { 10 fmt.Println("result: ", add.Name) //这里调用别名 11 fmt.Println("result: ", add.Age) 12 } 13 14 15 //add目录下的addDemo.go 16 package add 17 18 import ( 19 "fmt" 20 ) 21 22 // Name just Master's name 23 var Name string = "Loki" 24 25 // Age just Master's age 26 var Age int = 16 27 28 // init code will execute first 29 func init() { //Note! here "init" is lower case 30 fmt.Println("Welcome back! Master") 31 32 }
# exercise time -- exercise 5
# 包的初始化,不引用。 如果仅仅是想导入包,但是不小调用包的内容可以在包名前面加一个"_"
PS:多层导入,会以最里面(最深入的一层)的导入的init() 函数开始执行
1 package main 2 3 import ( 4 _ "go_dev/day2/example3/add" // 注意这里,导入了包,包名位置有个"_" 5 ) 6 7 func main() { 8 // fmt.Println("result: ", add.Name) //这里取消了调用 9 // fmt.Println("result: ", add.Age) 10 }
函数声明和注释
1. 函数声明:
格式: func 函数名字(参数列表)(返回值列表){}
无返回值
有参数,有返回值
有参数,有多个返回值
2. 注释,两种注释,单行注释: // 和多行注释/* */
3. 常量和变量
- 常量使用const 修饰, 代表永远是只读的,不能修改。
- const 只能修饰boolean number(int相关类型、浮点型、complex)和string
- 语法: const identifier [TYPE] = value,其中type可以省去
# exercise time -- exercise 6
# 常量实验
毫秒: Millisecond
微秒: Microsecond
1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 const ( 9 Male = 1 10 Female = 2 11 ) 12 13 func main() { 14 for { 15 if sec := time.Now().Unix(); sec%Female == 0 { 16 fmt.Print("Female") 17 } else { 18 fmt.Println("Man") 19 } 20 time.Sleep(time.Second) //防止死循环执行太快耗尽CPU 21 } 22 }
# iota
iota是golang语言的常量计数器,只能在常量的表达式中使用。iota在const关键字出现时将被重置为0(const内部的第一行之前),const中每新增一行常量声明将使iota计数一次(iota可理解为const语句块中的行索引)。使用iota能简化定义,在定义枚举时很有用。
# 写个例子
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 const ( 9 _ = iota // 丢弃第一个数字"0" 10 a 11 b 12 c 13 ) 14 fmt.Println(a, b, c) 15 16 }
# 变量
语法: var identifier type
PS: GO语言里面可以做类型推导
# exercise time -- exercise 7
Tips: 相当于是个获取CMD设置的%变量名% 的东东(os.Getenv)
# 所以,可以这样玩,举个例子
1 package main 2 3 import ( 4 "fmt" 5 "os" 6 ) 7 8 func main() { 9 // 变量的常规写法 10 var goos string = os.Getenv("OS") 11 // 变量的偷懒写法,Getenv里面的值就是cmd里面的%变量% 12 path := os.Getenv("PATH") 13 goPath := os.Getenv("GOPATH") 14 goRoot := os.Getenv("GOROOT") 15 fmt.Printf("The OS is : %s\n", goos) 16 fmt.Printf("The GO Path is : %s\n", goPath) 17 fmt.Printf("The GO Root is : %s\n", goRoot) 18 fmt.Printf("The OS Path is : %s\n", path) 19 20 }
值类型和引用类型
32位系统指针是4个字节
64位系统指针是8个字节
值类型:基本数据类型int 、float、bool、string以及数组和struct(结构体).
引用类型:指针、slice(切片)、map、chan等都是引用类型
# exercise time -- exercise 8
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 a := 100 //值类型 9 // 可以简写为 b := make(chain int,1) 10 var b chan int = make(chan int, 1) //引用类型 11 fmt.Println("a=", a) 12 fmt.Println("b=", b) 13 14 }
# 输出结果截图
# 附加演示有“游标”和无游标对函数调用以后对变量的影响
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 // Noncursor is 非定义游标的方式 8 func Noncursor(n int) { 9 n = 10 10 } 11 12 // Cursor is 定义使用游标的方式 13 func Cursor(n1 *int) { 14 *n1 = 11 15 } 16 17 func main() { 18 n := 99 19 n1 := 99 20 a := 100 //值类型 21 var b chan int = make(chan int, 1) //引用类型 22 fmt.Println("a=", a) 23 fmt.Println("b=", b) 24 Noncursor(n) 25 fmt.Println("n=", n) // 结果还是为99 没有变化 26 Cursor(&n1) 27 fmt.Println("n1=", n1) //结果为11 已经修改 28 29 }
PS: 堆 & 栈的区别
栈 先进后出 占空间去分配 <--- 值类型
堆 先进先出 系统的内存堆分配 <--- 引用类型
栈大小 C语言 几m Go语言几k
堆内存性能没有栈内存高
# exercise time -- exercise 9
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 // ChangeWay1 is change x and y value 8 func ChangeWay1(x, y int) (int, int) { 9 fmt.Println("Myself Way1---↓ Success") 10 x1, y1 := y, x 11 return x1, y1 12 } 13 14 // ChangeWay2 is change x and y value, --> Error Demo!!! <-- 15 func ChangeWay2(x, y int) { 16 fmt.Println("Error Way2---↓ Fail") 17 tmp := x 18 x = y 19 y = tmp 20 return 21 } 22 23 // ChangeWay3 is change x and y value, 采用了修改游标方式,直接修改了地址 24 func ChangeWay3(x, y *int) { 25 fmt.Println("Right Way3---↓ Sucess") 26 tmp := *x 27 *x = *y 28 *y = tmp 29 return 30 } 31 32 // ChangeWay4 is change x and y value 33 func ChangeWay4(x, y int) (int, int) { 34 fmt.Println("Myself Way4---↓ Success") 35 return y, x 36 } 37 38 func main() { 39 x := 3 40 y := 4 41 fmt.Println(ChangeWay1(x, y)) 42 ChangeWay2(5, 6) 43 fmt.Println(x, y) 44 fmt.Println(ChangeWay4(x, y)) // 注意这里方法3之所以要放到方法4下面,是因为方法3修改了地址指向内容,会导致地址指向的值产生变化 45 x, y = y, x // 这里值已经被修改 46 fmt.Println("Myself Way5---↓ Success", x, y) 47 ChangeWay3(&x, &y) 48 fmt.Println(x, y) // 这里再次做了一个交换,所以值又变了回去 49 50 }
# 执行结果
变量的作用域
1. 在函数内部声明的变量叫做局部变量,声明周期仅限于函数内部。
2. 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序(小写也是作用于整个程序,只是私有,外部访问不了)
3. 在函数内部语句块声明的变量仅在语句块中生效,语句块外不起作用
# 语句块 是被"{ }"里面的语句,例如:
func test(){ //局部变量 for i :=0; i < 100; i++{ //语句块 var b = i * 2 } }
# exercise time -- exercise 10
# 测试结果
# exercise time -- exercise 11
# 测试结果
# exercise time -- exercise 12
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 var a string 8 9 func main() { 10 a = "G" 11 print(a) 12 f1() 13 14 } 15 16 func f1() { 17 a := "O" 18 fmt.Println(a) 19 f2() 20 } 21 22 func f2() { 23 fmt.Println(a) 24 }
# 执行结果
4. 数据类型和操作符
bool类型,只能存true和false
2)相关操作符, !、&&、||
&&特性--> 短路操作: 前面表达式如果false,就不会去求后面的表达式
|| 特性 --> 前面表达式已经为true,后面的表达式就不用求了
关于这3个符号的优先级: !>&&>||.
# 逻辑与(&&) (有假即假)
# 逻辑或(||)(有真即真)
# 举个例子
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 var a bool = true 9 var b bool = false 10 // "&&" 有假即假, "||"有真即真 11 fmt.Printf(" !a的结果: %t\n !b的结果: %t\n a && b的结果: %t\n a||b的结果: %t\n", !a, !b, a && b, a || b) 12 13 }
# 执行结果截图
数字类型,主要有int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、float32、float64
PS: uint = 无符号整型; 有符号和无符号的区别(+、- 正负)
8位(1字节)
类型转换,type(variable),比如:var a int=8; var b int32=int32(a)
# exercise time -- exercise 13
# 上面输出结果肯定是编译错误,原因见下面代码:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 var n int16 = 34 9 var m int32 10 // m = n 这里没有使用类型转换,编译将报错 11 m = int32(n) // 这里使用了类型转换,可以正常输出结果 12 fmt.Printf("m is %d\n n is %d\n", m, n) 13 14 }
逻辑操作符: == 、!=、<、<=、>和>=
数学操作符:+、-、*、/等等
PS: 一个等于符号"=" 是赋值
# exercise time -- exercise 14
# 10 个显示结果不方便看,改为了3
# 里面有个 math/rand 的包,有点意思
1 package main 2 3 import ( 4 "fmt" 5 db "math/rand" //设置别名 6 "time" 7 ) 8 9 // 初始化工作 10 func init() { 11 db.Seed(time.Now().UnixNano()) // 让随机种子以纳秒方式产生随机(不设置这个将产生重复值) 12 13 } 14 15 func main() { 16 // 10个随机整数 17 for item := 0; item < 3; item++ { 18 res := db.Int() 19 fmt.Println(res) 20 } 21 // 10 个小于100的随机整数 22 for item := 0; item < 3; item++ { 23 res := db.Intn(100) 24 fmt.Println(res) 25 } 26 // 10 个随机浮点数 27 for item := 0; item < 3; item++ { 28 res := db.Float32() 29 fmt.Println(res) 30 } 31 32 }
5. 字符串类型
字符类型: var a byte
var a byte = `c` (只能使用单引号)
字符串类型: var str string
字符串表示两种方式: 1)双引号 2)`` (反引号)
双引号:
反引号:源生字符串
# 双引号和反引号
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 name := "Loki" 9 // 会保留换行操作动作,字符串原本的格式,换行不需要转义符 10 var str = `Hello Loki\n \n \n 11 this is a test string, 12 This is a test string too.` 13 fmt.Printf("Hello %s \n\n", name) // "" 双引号需要转义符 14 fmt.Printf(`str2= %s`, str) 15 }
# 关于byte和单引号不得不说的故事
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 name := "Loki" 9 luckyLetter := 'L' // 用单引号表示定义一个byte字符 10 fmt.Printf("My name is %s\n", name) 11 fmt.Println(luckyLetter) // 发现输出字符是数字?其实是ASCII编码 12 fmt.Printf("This is letter ->%c<-", luckyLetter) // %c 表示byte,使用格式化输出即可显示正确字母 13 }
# 详细的官方文档参考
https://go-zh.org/pkg/fmt/
# 从int 转换为str 例子:
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 var data int = 100 9 fmt.Println("value:", data) 10 fmt.Printf("数据类型是:%T\n", data) 11 str := fmt.Sprintf("%d", data) // int to str 12 fmt.Println("value: ", str) 13 fmt.Printf("数据额类型是:%T\n", str) 14 fmt.Printf("a=%q", str ) 15 16 }
# 2种字符串拼接方法
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 str1 := "Loki" 9 str2 := 16 10 str3 := fmt.Sprintf("%d", str2) 11 12 way1 := str1 + " " + str3 // string joint way 1 13 way2 := fmt.Sprintf("%s %s", str1, str3) // string joint way 2 14 fmt.Println(way1) 15 fmt.Println(way2) 16 17 }
# 字符串长度判断len()和切片
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 str1 := "Loki So Cool!" 9 fmt.Println("string length is : ", len(str1)) 10 fmt.Println(str1[0:3]) 11 fmt.Println(str1[5:7]) 12 fmt.Println(str1[8:]) 13 14 }
# 2种方法实现字符串反转功能函数
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 // reverse is function help string reversal 8 func reverse(str string) string { 9 var res string 10 strLen := len(str) 11 for i := 0; i < strLen; i++ { 12 res = res + fmt.Sprintf("%c", str[strLen-i-1]) 13 } 14 return res 15 } 16 17 func array(str string) string { 18 var res []byte //定义数组 19 tmp := []byte(str) 20 strLen := len(str) 21 for i := 0; i < strLen; i++ { 22 res = append(res, tmp[strLen-i-1]) // 从末尾到开头一个个字符加入数组 23 } 24 // fmt.Println(res) 输出结果默认是数组,所以返回值需要string()一下 25 return string(res) 26 } 27 28 func main() { 29 str1 := "Loki So Cool!" 30 result := reverse(str1) 31 fmt.Println("way1 result: ", result) 32 resultWay2 := array(str1) 33 fmt.Println("war2 result: ", resultWay2) 34 }
做题思路:
1. 搞清楚几个陌生术语,然后进行此题逻辑思考
素数: 只能被1整和自己整除的数,我们就认为它是素数
% 运算符 - 模除(取整除后的余数)
# 方法1
1 package main 2 3 import "fmt" 4 5 // 1. 判断 101-200 之间有多少个素数,并输出所有素数。 6 func isPrime(num int) bool { 7 if num <= 1 { 8 return false 9 } 10 for i := 2; i < num; i++ { 11 if num%i == 0 { 12 return false 13 } 14 } 15 return true 16 } 17 18 func main() { 19 count := 0 20 for x := 101; x < 200; x++ { 21 res := isPrime(x) 22 if res { 23 fmt.Printf("%d is Prime\n", x) 24 count++ 25 } 26 } 27 fmt.Printf("Total is %d", count) 28 29 }
“水仙花数” 解题思路,分别把输入的个位(%10)、十位(除以10,再%10)、百位(除以100,再%10)取出,分别再进行个位,十位,百位只积(各自数3次方之积),最后进行相加的结果等于数本身。
# 方法1
1 package main 2 3 import ( 4 "fmt" 5 "math" 6 ) 7 8 func isNarcissistc(n int) bool { 9 var i, j, k int 10 i = (n % 10) 11 j = (n / 10) % 10 12 k = (n / 100) % 10 13 sum := i*i*i + j*j*j + k*k*k // 次方可以使用math.Pow(x,y) x = 值 , y = 次方 14 // fmt.Println(math.Pow(float64(i), 3), math.Pow(float64(j), 3), math.Pow(float64(k), 3)) 15 return sum == n 16 } 17 18 func main() { 19 var startValue int 20 var endValue int 21 fmt.Scanf("%d%d", &startValue, &endValue) 22 for i := startValue; i < endValue; i++ { 23 if isNarcissistc(i) == true { 24 fmt.Println(i, "is Narcissistc Number") 25 } 26 } 27 }
# 方法2 采用了默认是字符,用字符ASCII编码转换后再计算的方法得到结果,再进行对比
1 package main 2 3 import ( 4 "fmt" 5 "strconv" // 用于字符串转换int的包 6 ) 7 8 func main() { 9 var str string 10 // fmt.Scanf("%s", &str) 11 str = "153" 12 13 var result = 0 14 for i := 0; i < len(str); i++ { 15 num := int(str[i] - '0') // ASCII 码 数字对应编码减去ASCII 数字0(十进制ASCII:48)都等于原来数本身, 这里字符'1' '5' '3'(ASCII:49 53 51);这里为了计算把str转换成了int 16 result += (num * num * num) 17 } 18 19 number, err := strconv.Atoi(str) //把字符串"153" 转换为数字153 20 if err != nil { 21 fmt.Printf("can not convert %s to int\n", str) 22 return 23 } 24 if result == number { // 对比 字符串转换的数字和计算结果的数字 25 fmt.Printf("%d is narcissistic number.√", number) 26 } else { 27 fmt.Printf("%d is not narcissistic number.×", number) 28 } 29 }
# 方法3 自己突发奇想的版本,主要借用了 strconv.Atoi(str) 功能
1 package main 2 3 import ( 4 "fmt" 5 "strconv" 6 ) 7 8 func wordToint(str string) int { 9 num, err := strconv.Atoi(str) 10 if err != nil { 11 fmt.Printf("can not convert %s to int\n", string(str)) 12 } 13 return num 14 } 15 16 func main() { 17 var str string 18 var result = 0 19 str = "154" 20 for i := 0; i < len(str); i++ { 21 num := wordToint(string(str[i])) 22 result += (num * num * num) 23 } 24 number := wordToint(str) 25 if result == number { 26 fmt.Printf("%d is narcissistic number.", number) 27 } else { 28 fmt.Printf("%d is not narcissistic number.", number) 29 } 30 }
阶乘(factorial) 解题思路,搞清楚什么是“阶乘” ,下面几个定义:
阶乘是基斯顿·卡曼(Christian Kramp,1760~1826)于 1808 年发明的运算符号,是数学术语。
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘写作n!。
0! = 1
n! = 1 x 2 x 3 x ... x(n - 1)n
# 方法一(使用递归)
1 package main 2 3 import "fmt" 4 5 func factorial(n int) int { 6 if n <= 1 { 7 return 1 8 } 9 return factorial(n-1) * n 10 } 11 12 func main() { 13 // fmt.Println(factorial(5)) 14 var n int 15 countSum := 0 // 计算并记录阶乘之和 16 fmt.Scanf("%d", &n) // 用户输入的n值,并改变函数内的值用 "&" 符号 17 for i := 1; i <= n; i++ { 18 countSum = countSum + factorial(i) 19 } 20 fmt.Println("factorial value sum is : ", countSum) 21 }
# 方法二(使用循环)
1 package main 2 3 import "fmt" 4 5 func factorial(n int) int { 6 var sum int = 0 // 用于最后的求和结果 7 var lastValues int = 1 // 用于记录上一次的乘积 8 9 if n <= 1 { // 排除用户输入0和1 让0! 和 1! 直接返回结果 10 return 1 11 } 12 13 for i := 1; i <= n; i++ { 14 lastValues = lastValues * i 15 fmt.Printf("%d!=%v\n", i, lastValues) 16 sum += lastValues 17 } 18 return sum 19 }
# 执行结果截图