Go语言学习之2 包、函数、常量、数据类型、字符操作
第一部分:基本数据类型和操作符
1. 文件名&关键字&标识符
(1)所有go源码以.go结尾
(2)标识符以字母或下划线开头,大小写敏感,比如:
a. boy b. Boy c. a+b d. 0boy e. _boy f. =_boy g. _
其中:a b e符合表示符的命名规范
(3) _是特殊标识符,用来忽略结果
(4)保留关键字
2. Go程序基本结构
package main import ( "fmt" ) func main() { fmt.Println("hello world!") }
(1) 任何一个代码文件隶属于一个包
(2)import 关键字,引用其他包:
import("fmt")
import("os")
通常习惯写成:
import (
"fmt"
"os"
)
(3) golang可执行程序,package main,
并且有且只有一个main入口函数
(4)包中函数调用:
a. 同一个包中函数,直接调用
b. 不同包中函数,通过包名+点+函数名进行调用
(5)包访问控制规则:
a. 大写意味着这个函数/变量是可导出的
b. 小写意味着这个函数/变量是私有的,包外部不能访问
准备:先创建F:\Go\project\src\go_dev\day2目录,并在该目录下创建example1,example2目录,并在example2目录下创建add,main,test目录
练习1. 写一个程序,对于给定一个数字n,求出所有两两相加等于n的组合。比如: 对于n=5,所有组合如下所示:
0 + 5 = 5
1 + 4 = 5
2 + 3 = 5
3 + 2 = 5
4 + 1 = 5
5 + 0 = 5
在F:\Go\project\src\go_dev\day2\example1目录下创建main.go文件
1 package main 2 3 import "fmt" 4 5 func list(n int) { 6 for i := 0; i <= n; i++ { 7 fmt.Printf("%d + %d = %d\n", i, n - i, n) 8 } 9 } 10 11 func main() { 12 list(10) 13 }
练习2:一个程序包含两个包add和main,其中add包中有两个变量:Name和age。请问main包中如何访问Name和age?
先来看下面例子1:
1 package main 2 3 import "fmt" 4 5 func test() { 6 fmt.Println(str) 7 } 8 9 func main() { 10 test() 11 } 12 13 var str string = "hello world"
运行结果:
注意: 我们定义了一个全局变量 str 为字符串类型,可以看出在程序运行时,全局变量是先被初始化, 且不像C语言,全局变量初始化与其位置无关。
例子2:
在add目录下创建add.go文件,内容如下
1 package add 2 3 var Name string = "hello world" 4 var age int = 10
在main目录下创建main.go文件,内容如下
1 package main 2 3 import ( 4 "fmt" 5 "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 fmt.Println("Name = ", add.Name) 10 fmt.Println("age = ", add.age) 11 }
运行该程序:
报错的原因是:在go中变量首字母大写表示该变量可以导出,由于age首字母小写无法导出所以报错,解决方法就是将 age -> Age。
改完之后运行结果如下:
练习3: 包别名的应用,开发一个程序,使用包别名来访问包中的函数?
其中add.go不变,main.go如下: 其中在引入包前面加别名(a "go_dev/day2/example2/add")
1 package main 2 3 import ( 4 "fmt" 5 a "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 fmt.Println("Name = ", a.Name) 10 fmt.Println("age = ", a.Age) 11 }
运行结果:
练习4: 每个源文件都可以包含一个init函数,这个init函数自动被go运行框架调用。开发一个程序演示这个功能?
首先再来看下练习2:
add.go
1 package add 2 3 var Name string 4 var Age int 5 6 Name = "hello world" 7 Age = 10
main.go
1 package main 2 3 import ( 4 "fmt" 5 a "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 fmt.Println("Name = ", a.Name) 10 fmt.Println("age = ", a.Age) 11 }
运行结果:
结果分析:报错的原因是 Name = "hello world" 和 Age = 10没有放在函数体里面
修改add.go为:
1 package add 2 3 var Name string 4 var Age int 5 6 func Change() { 7 Name = "hello world" 8 Age = 10 9 }
注意:add.go方法名Change首字母必须大写,否则无法导出被外部调用。
修改main.go为:
1 package main 2 3 import ( 4 "fmt" 5 a "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 a.Change() 10 fmt.Println("Name = ", a.Name) 11 fmt.Println("age = ", a.Age) 12 }
运行结果:
再来看练习4:
add.go将Change修改为init,init函数自动被go运行框架调用
1 package add 2 3 var Name string 4 var Age int 5 6 func init() { 7 Name = "hello world" 8 Age = 10 9 }
main.go
1 package main 2 3 import ( 4 "fmt" 5 a "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 fmt.Println("Name = ", a.Name) 10 fmt.Println("age = ", a.Age) 11 }
运行结果:
练习5: 包的只初始化,不引用。请开发一个程序,演示这个做法?
add.go
1 package add 2 3 import( 4 _ "go_dev/day2/example2/test" 5 ) 6 7 func init () { 8 Name = "hello world" 9 Age = 20 10 11 } 12 13 var Name string = "xxxxx" 14 var Age int = 100
test.go(在test目录下创建)
1 package test 2 3 import( 4 "fmt" 5 ) 6 7 var Name string = "this is in test package" 8 var Age int = 1000 9 10 func init() { 11 fmt.Println("this is a test") 12 fmt.Println("test.package.Name=", Name) 13 fmt.Println("test.package.age=", Age) 14 15 Age = 10 16 fmt.Println("test.package.age=", Age) 17 }
main.go
1 package main 2 3 import ( 4 "fmt" 5 a "go_dev/day2/example2/add" 6 ) 7 8 func main() { 9 fmt.Println("Name = ", a.Name) 10 fmt.Println("age = ", a.Age) 11 }
下运行结果:
结果分析:
(1)在add.go中的 _ 表示包的只初始化(只执行test.go中的init函数),不引用。
(2)变量 Name和Age在每个包里面是相互独立的,在test.go中修改Name和Age并不会影响add.go中的这两个变量
(3)在add.go中,init函数是在全局变量Name和Age的前面,但是其值依然被改变(执行顺序是先初始化全局变量->init,因此值会被改变)。
3. 函数声明和注释
(1)函数声明: func 函数名字 (参数列表) (返回值列表){}
例如:
func add() { } func add(a int, b int) int { } func add(a int, b int) int, int { }
(2)注释,两种注释,单行注释: // 和多行注释 /* */
//add 计算两个整数之和,并返回结果 func add(a int, b int){ } /*add 计算两个整数之和, 并返回结果*/ func add(a int, b int){ }
4. 常量和变量
常量:
(1)常量使用const 修饰,代表永远是只读的,不能修改。
(2)const 只能修饰boolean,number(int相关类型、浮点类型、complex)和string。
(3)语法:const identifier [type] = value,其中type可以省略。
const b string = "hello world" const b = "hello world" const Pi = 3.1415926 const a = 9/3 const c = getValue() //error
(4)比较优雅的写法:
const ( a = 0 b = 1 c = 2 )
(5)更加专业的写法:
const ( a = iota b //1 c //2 )
练习6:定义两个常量Man=1和Female=2,获取当前时间的秒数,如果能被Female整除,则在终端打印female,否则打印man。
1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 const ( 9 Man = 1 10 Female = 2 11 ) 12 13 func main() { 14 for { 15 cur_time := time.Now().Unix() 16 if (cur_time % Female == 0) { 17 fmt.Println("Female") 18 } else { 19 fmt.Println("Man") 20 } 21 //time.Sleep(1000 * time.Millisecond) 22 time.Sleep(time.Second) 23 } 24 }
变量:
语法:var identifier type
例1:
var a int var b string var c bool var d int = 8 var e string = "hello world"
例2:
var ( a int //默认为0 b string //默认为"" c bool //默认为false d int = 8 e string = "hello world" )
例3:
var ( a int //默认为0 b string //默认为"" c bool //默认为false d = 8 e = "hello world" )
练习7:写一个程序获取当前运行的操作系统名称和PATH环境环境变量的值,并打印在终端。
1 package main 2 3 import ( 4 "fmt" 5 "os" 6 ) 7 8 func main() { 9 var go_root = os.Getenv("GOROOT"); 10 fmt.Printf("The go root is: %s\n", go_root) 11 var path = os.Getenv("PATH") 12 fmt.Printf("Path is: %s\n", path) 13 }
5. 值类型和引用类型
值类型:变量直接存储值,内存通常在栈中分配。比如:基本数据类型int、float、bool、string以及数组和struct。
引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过GC回收。比如:引用类型:指针、slice、map、chan等都是引用类型。
例8: 写一个程序用来打印值类型和引用类型变量到终端,并观察输出结果。
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func modify(a int) { 8 a = 10 9 } 10 11 func modify1(a *int) { 12 *a = 10 13 } 14 15 func main() { 16 a := 5 17 b := make(chan int, 1) 18 19 fmt.Println("a=", a) //a= 5 20 fmt.Println("b=", b) //b= 0xc0420320e0 21 22 modify(a) 23 fmt.Println("a=", a) //a= 5 24 modify1(&a) 25 fmt.Println("a=", a) //a= 10 26 }
例9:写一个程序,交换两个整数的值。比如: a=3; b=4; 交换之后:a=4;b=3。
1 package main 2 3 import "fmt" 4 5 //交换a和b的值(通过指针) 6 func swap(a *int, b *int) { 7 tmp := *a 8 *a = *b 9 *b = tmp 10 } 11 12 //交换a和b的值 13 func swap1(a int, b int) (int, int) { 14 return b, a 15 } 16 17 //a和b的值不会发生变化 18 func swap2(a int, b int) { 19 tmp := a 20 a = b 21 b = tmp 22 } 23 24 func test() { 25 var a int8 = 100 26 // cannot use a (type int8) as type int16 in assignment 27 //var b int16 = a 28 var b int16 = int16(a) //和C语言有所不同 29 fmt.Printf("a=%d b=%d\n", a, b) 30 } 31 32 func main() { 33 first := 100 34 second := 200 35 //swap(&first, &second) //first=200 second=100 36 //first, second = swap1(first, second) //first=200 second=100 37 first, second = second, first //first=200 second=100 38 fmt.Println("first=", first) 39 fmt.Println("second=", second) 40 41 test() 42 }
6. 变量的作用域
(1)在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部。
(2)在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果是大写的,则作用于整个程序。
练习10: 请指出下面程序的输出是什么?
1 var a = "G" 2 package main() { 3 n() 4 m() 5 n() 6 } 7 8 func n() { 9 fmt.Println(a) 10 } 11 12 func m() { 13 a = "0" 14 fmt.Println(a) 15 }
练习11:请指出下面程序的输出是什么?
1 var a = "G" 2 package main() { 3 n() 4 m() 5 n() 6 } 7 8 func n() { 9 fmt.Println(a) 10 } 11 12 func m() { 13 a := "0" 14 fmt.Println(a) 15 }
练习12:请指出下面程序的输出是什么?
1 package main 2 3 var a string 4 5 import "fmt" 6 7 func main() { 8 a = "G" 9 fmt.Println(a) 10 f1() 11 } 12 13 func f1() { 14 a := "O" 15 fmt.Println(a) 16 f2() 17 } 18 19 func f2() { 20 fmt.Println(a) 21 }
7. 数据类型和操作符
数据类型:
(1)bool类型,只能存true和false
(2)相关操作符, !、&&、||
(3)数字类型,主要有int、int8、int16、int32、int64、uint8、uint16、uint32、uint64、float32、float64
(4)类型转换,type(variable),比如:var a int=8; var b int32=int32(a)
package main func main() { a int b int32 a = 15 b = a + a //compiler error b = b + 5 //ok: 5 is constant }
(5)字符类型:var a byte 例如:var a byte = 'c'
(6)字符串类型: var str string 例如: var str string = "hello world"
字符串表示两种方式: 1)双引号 会识别转义字符
2)``(反引号) 以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果
例如:
1 package main 2 3 import "fmt" 4 5 func main() { 6 var str = "hello world\n" 7 var str1 = ` 8 床前明月光, 9 疑是地上霜。 10 举头望明月, 11 我是郭德纲。 12 ` 13 var b byte = 'c' 14 15 fmt.Println(str) 16 fmt.Println(str1) 17 fmt.Println(b) //99 18 fmt.Printf("%c\n", b) 19 }
(7)派生类型
包括:
(a) 指针类型(Pointer)
(b) 数组类型
(c) 结构化类型(struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
1 package main 2 3 import "fmt" 4 5 //字符串反转 6 func reverse(str string) string { 7 var result string 8 strLen := len(str) 9 for i := 0; i < strLen; i++ { 10 result = result + fmt.Sprintf("%c", str[strLen-i-1]) 11 } 12 return result 13 } 14 15 //字符串反转 16 func reverse1(str string) string { 17 var result []byte 18 tmp := []byte(str) 19 length := len(str) 20 for i := 0; i < length; i++ { 21 result = append(result, tmp[length-i-1]) 22 } 23 return string(result) 24 } 25 26 func main() { 27 var str1 = "hello" 28 str2 := "world" 29 30 //str3 := str1 + " " + str2 31 str3 := fmt.Sprintf("%s %s", str1, str2) 32 n := len(str3) 33 34 fmt.Println(str3) 35 36 fmt.Printf("len(str3)=%d\n", n) 37 38 substr := str3[0:5] 39 fmt.Println(substr) 40 41 substr = str3[6:] 42 fmt.Println(substr) 43 44 result := reverse(str3) 45 fmt.Println(result) 46 47 result = reverse1(result) 48 fmt.Println(result) 49 }
(g) 接口类型(interface)
(h) Map 类型
格式化输出:
通用: %v 值的默认格式表示。当输出结构体时,扩展标志(%+v)会添加字段名 %#v 值的Go语法表示 %T 值的类型的Go语法表示 %% 百分号 布尔值: %t 单词true或false 整数: %b 表示为二进制 %c 该值对应的unicode码值 %d 表示为十进制 %o 表示为八进制 %q 该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示 %x 表示为十六进制,使用a-f %X 表示为十六进制,使用A-F %U 表示为Unicode格式:U+1234,等价于"U+%04X" 浮点数、复数的两个组分: %b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat %e 科学计数法,如-1234.456e+78 %E 科学计数法,如-1234.456E+78 %f 有小数部分但无指数部分,如123.456 %F 等价于%f %g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出) %G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出) 字符串和[]byte: 1 %s 直接输出字符串或者[]byte %q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示 2 %x 每个字节用两字符十六进制数表示(使用a-f) 3 %X 每个字节用两字符十六进制数表示(使用A-F) 指针: %p 表示为十六进制,并加上前导的0x 其它flag: + 总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义); - 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐); # 切换格式: 八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p); 对%q(%#q),如果strconv.CanBackquote返回真会输出反引号括起来的未转义字符串; 对%U(%#U),如果字符是可打印的,会在输出Unicode格式、空格、单引号括起来的go字面值; ' ' 对数值,正数前加空格而负数前加负号; 对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格; 0 使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;
例如:
1 package main 2 3 import "fmt" 4 5 func main() { 6 var a int = 100 7 var b bool 8 c := 'a' 9 10 fmt.Printf("%+v\n", a) //100 11 fmt.Printf("%#v\n", b) //false 12 fmt.Printf("%T\n", c) //int32 13 fmt.Printf("90%%\n") //90% 14 fmt.Printf("%t\n", b) //false 15 fmt.Printf("%b\n", 100) //1100100 16 fmt.Printf("%f\n", 199.22) //199.220000 17 fmt.Printf("%q\n", "this is a test") //"this is a test" 18 fmt.Printf("%x\n", 39839333) //25fe665 19 fmt.Printf("%p\n", &a) //0xc04203c1d0 20 21 str := fmt.Sprintf("a=%d", a) //"a=100" 22 fmt.Printf("%q\n", str) 23 }
操作符:
(5)逻辑操作符: == 、!=、<、<=、>和 >=。
(6)数学操作符:+、-、*、/等等。
练习13:使用math/rand生成10个随机整数,10个小于100的随机整数以及10个随机浮点数?
1 package main 2 3 import ( 4 "fmt" 5 "math/rand" 6 "time" 7 ) 8 9 func init() { 10 rand.Seed(time.Now().UnixNano()) 11 } 12 13 func main() { 14 for i := 0; i < 10; i++ { 15 a := rand.Int() 16 fmt.Println(a) 17 } 18 19 for i := 0; i < 10; i++ { 20 a := rand.Intn(100) 21 fmt.Println(a) 22 } 23 24 for i := 0; i < 10; i++ { 25 a := rand.Float32() 26 fmt.Println(a) 27 } 28 29 }
练习:
14. 判断 101-200 之间有多少个素数,并输出所有素数。
1 package main 2 3 import ( 4 "fmt" 5 "math" 6 ) 7 8 func isPrime1(num int) bool { 9 for i := 2; i < num; i++ { 10 if (num % i == 0) { 11 return false 12 } 13 } 14 return true 15 } 16 17 //optimization 18 func isPrime2(num int) bool { 19 for i := 2; i <= int(math.Sqrt(float64(num))); i++ { 20 if (num % i == 0) { 21 return false 22 } 23 } 24 return true 25 } 26 27 func main(){ 28 var m int 29 var n int 30 var primeNum int 31 fmt.Println("Please input start and end num >>") 32 fmt.Scanf("%d%d", &m, &n) 33 34 for i := m; i <= n; i++ { 35 /* 36 if (isPrime(i)) { 37 fmt.Println(i) 38 primeNum += 1 39 }*/ 40 41 if (isPrime2(i)) { 42 fmt.Println(i) 43 primeNum += 1 44 } 45 } 46 47 fmt.Println("primeNum = ", primeNum) 48 }
15. 打印出100-999中所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字立方和等于该数本身。例如:153 是一个“水仙花数”,因为 153=1 的三次方+5 的三次方+3 的三次方。
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func isNarcNumber(num int) bool { 8 var i, j, k int 9 10 i = num%10 11 j = num/10%10 12 k = num/100 13 sum := i*i*i + j*j*j + k*k*k 14 15 return (sum == num) 16 } 17 18 func main() { 19 var m, n int 20 var totalNum int 21 fmt.Println("Input a number(100-999) >>") 22 fmt.Scanf("%d%d", &m, &n) 23 for i := m; i <= n; i++ { 24 if (isNarcNumber(i)) { 25 fmt.Println(i) 26 totalNum += 1 27 } 28 } 29 30 fmt.Println("total narcissus number ", totalNum) 31 }
1 package main 2 3 import ( 4 "fmt" 5 "strconv" 6 ) 7 8 func main() { 9 var str string 10 fmt.Println("Input a number(100-999) >> ") 11 fmt.Scanf("%s", &str) 12 13 var result = 0 14 for i := 0; i < len(str); i++ { 15 num := int(str[i] - '0') 16 result += (num * num * num) 17 } 18 19 number, err := strconv.Atoi(str) 20 if err != nil { 21 fmt.Printf("Can not convert %s to int\n", str) 22 return 23 } 24 25 if result == number { 26 fmt.Printf("%d is narcissus\n", number) 27 } else { 28 fmt.Printf("%d is not narcissus\n", number) 29 } 30 }
16. 对于一个数n,求n的阶乘之和,即: 1! + 2! + 3!+…n !
1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func computFact(num int) uint64 { 8 var s uint64 = 1 9 var sum uint64 = 0 10 for i := 1; i <= num; i++ { 11 s *= uint64(i) 12 fmt.Printf("%d!=%v\n", i, s) 13 sum += s 14 } 15 16 return sum 17 } 18 19 func main() { 20 var num int 21 fmt.Println("Input a number (greater than 1) >> ") 22 fmt.Scanf("%d", &num) 23 24 s := computFact(num) 25 fmt.Println(s) 26 }