02 | 基本数据类型和操作符
一、文件名&关键字&标识符
- 所有go源码都是以.go结尾
- 标识符以字母或下划线开头,大小写敏感
- 下划线_是特殊标识符,用户忽略结果
- 保留关键字
- 导入包时可以设置别名
下面是保留关键字:
二、GO程序的基本结构
package main import ( "fmt" ) func main(){ fmt.Println("Hello world") }
- 任何一个代码必须隶属一个包
- import关键字,引入其他包
- golang可执行程序,package main 并且只有一个main入口函数
- 包中函数的调用,同一个函数中可以直接调用;不同包中函数,通过包名+点+函数名进行调用
- 包访问控制规则,大写意味着这个函数或者变量可以导出的,及在其他包内是可以调用的;小写以为这个函数或者变量是私有的,包外部不能访问。
小练习1
写一个小程序,对于给定的一个数字n,求出所有两两相加等于n的组合
package main import ( "fmt" ) func add_num(n int){ for i:=0;i<=n;i++{ fmt.Printf("%d+%d=%d\n",i,n-i,n) } } func main(){ add_num(5) }
输出结果如下:
小练习2
写一个小程序,包含两个包,add和main,其中add包中有两个变量:Name,和age,请问main包中如何访问Name和age?(用于理解go中大小写敏感的问题)
example2/add/add.go
package add var Name string = "zhangbiao" var age int = 23
main/main.go
package main import ( "fmt" "go_dev/day02/example2/add" ) func main() { fmt.Println(add.Name) }
输出结果如下
从结果我们可以发现我们在main包中是不能调用到add包中的age,但是是可以调用到Name
这里就是因为大小写的问题,go中的大写可以理解为其他语言中的public,小写理解为private
这里有个问题需要注意:
我们把add包中的代码改为:
package add var Name string var Age int Name = "zhangbiao" Age = 23
这样也是错误的写法,go作为编译型语言,必须通过函数来进行语句的执行,而不能在函数外执行语句
小练习3 (对包起一个别名)
开发一个小程序,使用包别名来访问包中的函数或变量
直接对上一个程序的main包进行更改
package main import ( "fmt" a "go_dev/day02/kexia02/add" ) func main() { fmt.Println(a.Name) fmt.Println(a.Age) }
小练习4
每个源文件都可以包含一个init函数,这个init函数自动被go运行框架调用,通过下面例子演示:
package main import ( "fmt" ) func init(){ fmt.Println("执行初始化函数") } func main() { fmt.Println("hello world") }
运行结果是先打印了init函数的内容,后打印了main函数中的问题,所以init函数先与main函数执行
输出结果如下
三、函数的声明和注释
函数声明
格式为:func 函数名字(参数列表)(返回值列表)
例子如下:
func add(){ } func add(a int,b int) int{ } func add(a int,b int) (int int){ }
注释
单行注释//
多行注释/* */
四、GO语言的数据类型和操作符
常见数据类型及分类
GO语言按照类别分为几下几种数据类型:
布尔型:true或false,例子:var b bool = true
数字类型:包括整型int和浮点float
字符串类型:这里强调一下,GO的字符串是由单个字节连接起来的Go语言的字符串的字节使用UTF-8编码标识Unicode文本。
派生类型:这里包括指针类型,数组类型,结构化类型,Channel类型,函数类型,接口类型,Map类型
注意:
字符串的时候用双引号”“,这里也可以用反引号`,通过反引号的方式会保留你的格式,不会对你的内容做任何转义
字节的时候用单引号‘’,同样也可以通过反引号`
var cc byte = 'c' fmt.println(ccc)
var cc byte = c
fmt.println(cc)
一个会打印c的ASCII,一个会打印c
关于fmt.Printf()的用法
官网地址:https://go-zh.org/pkg/fmt/
一般
%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 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat
的 'b' 转换格式一致。例如 -123456p-78
%e 科学计数法,例如 -1234.456e+78
%E 科学计数法,例如 -1234.456E+78
%f 有小数点而无指数,例如 123.456
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出
字符串与字节切片
%s 字符串或切片的无解译字节
%q 双引号围绕的字符串,由Go语法安全地转义
%x 十六进制,小写字母,每字节两个字符
%X 十六进制,大写字母,每字节两个字符
指针
%p 十六进制表示,前缀 0x
通过fmt.Printf() 可以格式化输出到终端,如果想要格式化存储到变量则是fmt.Sprintf()
数字类型
数字类型包括了:
uint8(无符号8位整型,0到255)
uint16(无符号16位整型,0到65535)
uint32(无符号32位整型,0到4294967295)
unint64(无符号64位整型,0到18446744073709551615)
int8(有符号8位整型,-128到127)
int16(有符号16位整型,-32768到32767)
int32(有符号32位整型 ,-2147483648 到 2147483647)
int64(有符号64位整型 ,-9223372036854775808到9223372036854775807)
浮点型
flat32: 32位浮点型数
flag64: 64位浮点型数
complex64:32 位实数和虚数
complex128:64 位实数和虚数
bool类型
只能存true和false
类型转换
举个例子来理解: var a int = 8 转换为int32 var b int32 = int32(a)
当我们代码中设计到数据计算的时候,要保证两个数据类型完全相同,
var a int 和 var b int32 是不能直接做计算处理的,这个时候就需要用到类型转换
相关操作符
! && || 分别表示非,与,或
== 、=、!=、 <、>、<=、>=
练习1
使用math/rand生成随机整数,10个小于100的随机整数以及10个随机浮点数
package main import ( "fmt" "math/rand" "time" ) func init() { rand.Seed(time.Now().Unix()) } func rand_print(){ for i:=0;i<10;i++{ fmt.Println(rand.Int31n(100)) fmt.Println(rand.Float32()) } } func main() { rand_print() }
但是这里会造成每次运行程序生成的随机数是相同的,解决方式是加入随机种子:rand.Seed(time.Now().Unix())
五、常量
常用cost修饰,代表永远是只读不能修改
const只能修饰boolean,number(int相关类型,浮点类型,complex)和string
语法
const 变量名 [变量类型] = value其中变量类型可以省略
菜鸟写法
const b string = "hello" const b int = 23
通常定义常亮的写法
const ( a = 0 b = 1 )
专业写法
const ( a = iota b c )
这里自动回吧a赋值为0,后面的变量一次加1
例子
定义两个常量Man=1和Female=2,获取当前时间的秒数,如果能被Female整除,则在终端打印female,否则打印man。
package main import( "fmt" "time" ) const ( Man = 1 Female = 2 ) func main() { for { second := time.Now().Unix() if (second % Female == 0) { fmt.Println("female") } else { fmt.Println("man") } time.Sleep(1000 * time.Millisecond) } }
六、变量
声明一个变量的形式是使用var关键字格式如下:
var 变量名 变量类型
关于变量的声明常用的有三种方法:
- 指定变量类型,声明变量后不赋值,使用默认值。如果是数据类型是int,默认值为0,如果数据类型是字符串string,默认值为空,如果是布尔类型默认为false
- 不指定变量类型,根据值自行判断变量类型(个人不建议这种方法,go是强类型语言,定义变量最好指定变量类型)
- 省略var关键字通过:=方式定义变量,并赋值。例子name := "zhaofan"就相当于var name string然后name = "zhaofan"
变量的作用域
在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部
在函数外部生命的变量叫做全局变量,生命周期作用域整个包,如果是大写,作用域整个程序
在函数内的语句块的内的变量只在语句块,生命周期就是仅限于语句块内.这里主要指{} 括起来的语句块
通过下面的例子理解
例子1
package main import ( "fmt" ) var a string = "zhangbiao" func n(){ fmt.Println(a) } func m(){ a := "Jack" fmt.Println(a) } func main(){ n() m() n() }
输出结果如下
这里就是验证了函数内部的定义的变量,生命周期仅限于函数内部
如果我们把m函数中的a:="Jack"改为a = "Jack"
结果为:zhangbiao,Jack,Jack
例子2
package main import ( "fmt" ) var a string func f1(){ a := "Jack" fmt.Println(a) f2() } func f2(){ fmt.Println(a) } func main(){ a = "zhangbiao" fmt.Println(a) f1() }
输出结果如下
这个程序的结果会打印zhaofan,Jack,zhangbiao
其实这里还是要理解上面的说的关于变量的作用域
值类型和引用类型
值类型:变量直接存储值,即变量直接指向存在内存中的值。内存通常在栈中分配
值类型通常包括基本数据类型int,float,bool,string以及数组和struct
var i int = 5
var j int = i
这个时候i和j的值都为5
其实这里内存中是将i的值进行了拷贝,我们通过&获取变量的内存地址也可以看出
&i和&j 的内存地址是不同的,所以我如果在var j int = i,后继续更改i变量的值,并不会影响j的值,因为是不同的内存地址存放的值
引用类型:变量存的是一个地址,这个地址存储最终的值,内存通常在堆上分配。通过GC回收
一个引用类型的变量 r1 存储的是 r1 的值所在的内存地址(数字),或内存地址中第一个字所在的位置。
引用类型通常包括指针,slice,map,chan等。
所以如果我们定义了两个引用类型变量像上面的值类型一样进行赋值操作则两者都会改变,因为两者的引用地址是相同的。
所以在引用类型中变量是拷贝的内存地址,而地址最终指向值
值类型:基本数据类型int、float、bool、string以及数组和struct。
引用类型:指针、slice、map、chan等都是引用类型。
小练习1
package main import ( "fmt" ) func main() { var a int = 100 var b chan int = make(chan int,1) fmt.Println("a=",a) fmt.Println("b=",b) }
a是值类型b是引用类型
输出结果
小练习2
写一个小程序,交换两个整数的值
package main import ( "fmt" ) func swap(a int,b int){ a,b = b,a fmt.Println("a=",a,"b=",b) } func main() { swap(5,4) }
输出结果为
小案例
1. 判断某个区间有多少个素数,并输出所有素数。
func Sqrt 返回x的二次方根,特例如下:
package main import ( "fmt" "math" ) func isPrime(n int) bool { for i := 2; i <= int(math.Sqrt(float64(n))); i++ { if n%i == 0 { return false } } return true } func main() { var n int var m int fmt.Scanf("%d%d%s", &n, &m) for i := n; i < m; i++ { if isPrime(i) == true { fmt.Printf("%d\n", i) continue } } }
输出结果
2. 打印出100-999中所有的“水仙花数”,所谓“水仙花数”是指一个三位数,其各位数字
立方和等于该数本身。例如:153 是一个“水仙花数”,因为 153=1 的三次
方+5 的三次方+3 的三次方。
package main import "fmt" func isNumber(n int) bool { var i, j, k int i = n % 10 # 获取个位 j = (n / 10) % 10 # 获取十位 k = (n / 100) % 10 # 获取百位 sum := i*i*i + j*j*j + k*k*k return sum == n } func main() { var n int var m int fmt.Scanf("%d,%d", &n, &m) for i := n; i < m; i++ { if isNumber(i) == true { fmt.Println(i) } } }
输出结果如下
3. 对于一个数n,求n的阶乘之和,即: 1! + 2! + 3!+…n!
package main import "fmt" func sum(n int) uint64 { var s uint64 = 1 var sum uint64 = 0 for i := 1; i <= n; i++ { s = s * uint64(i) fmt.Printf("%d!=%v\n", i, s) sum += s } return sum } func main() { var n int fmt.Scanf("%d", &n) s := sum(n) fmt.Println(s) }
输出结果如下