1). 文件名 & 关键字 & 标识符
1. 所有go源码都以 .go 结尾 2. 标识符以字母或下划线开头,大小写敏感 3. _ 是特殊标识符,用来忽略结果 4. 保留关键字
golang 关键字如下:
包中函数的调用:
a. 同一个包中的函数,可直接调用 (大小写都可以)
b. 不同包中的函数,通过 包名 + 点 + 函数名进行调用 (函数名的首字母必须大写)
包访问控制规则:
a. 大写意味着这个函数/变量是可导出的
b. 小写意味着这个函数/变量是私有的,包外部不能访问
示例1:写一个程序,对于给定一个数字n,求出所有两两相加等于n的组合。
代码如下:
package main import ( "fmt" ) func handle(n int){ for i := 0;i<=n;i++ { fmt.Printf("%d+%d=%d\n",i,n-i,n) // fmt.Printf() 是格式化输出 } } func main(){ handle(5) }
编译运行:
[root@NEO project]# go build -o bin/example01_plus go_dev/day02/example01_plus/main [root@NEO project]# bin/example01_plus 0+5=5 1+4=5 2+3=5 3+2=5 4+1=5 5+0=5 [root@NEO project]#
示例2:一个程序包含两个包 add 和 main,其中 add 包中有两个变量: Name 和 age。问 main 包中如何访问 Name 和 age?
# 目录结构如下: [root@NEO day02]# tree example02_call_var01 example02_call_var01 ├── add │ └── add.go └── main └── main.go 2 directories, 2 files [root@NEO day02]# 示例代码如下: # 方式1: main.go 文件: package main import ( "go_dev/day02/example02_call_var01/add" "fmt" ) func main(){ fmt.Println("Name=",add.Name) fmt.Println("Age=",add.Age) } add.go 文件: package add var Name string = "hello world" // 声明变量,并对变量的值进行初始化(编译时);字符串如果只声明却没初始化,则该字符串变量的值为 空 var Age int = 10 // 字符串如果只声明却没初始化,则该字符串变量的值为 0 # 编译之后的运行结果如下: [root@NEO bin]# ./example0201 Name= hello world Age= 10 [root@NEO bin]# # 方式2: main.go 文件: package main import ( "go_dev/day02/example02_call_var02/add" "fmt" ) func main(){ add.Var_test() // 先调用 该函数来对变量 Name 和 Age 进行初始化 fmt.Println("Name=",add.Name) fmt.Println("Age=",add.Age) } add.go 文件: package add var Name string var Age int func Var_test(){ // 首字母要大写 Name = "hello world" // go 是编译型语言,所有的执行语句都要放到函数里面(赋值/初始化也是执行语句) Age = 10 } # 错误示例: main.go 文件内容: package main import ( "go_dev/day02/example02_call_var03_err/add" "fmt" ) function main(){ fmt.Println("Name=",add.Name) fmt.Println("Age=",add.Age) } add.go 文件内容: package add var Name string var Age int Name = "hello world" // go 是编译型语言,所有的执行语句都要放到函数中作为入口 Age = 10
示例3:包别名的应用:开发一个程序,使用包别名来访问包中的函数
# main.go示例如下: package main import ( a "go_dev/day02/example02_call_var01/add" // 给go_dev/day02/example02_call_var01/add这个包起一个别名 a "fmt" ) func main(){ fmt.Println("Name=",a.Name) // 使用包的别名来调用包中的函数 fmt.Println("Age=",a.Age) }
示例4:每个源文件都可以包含一个 init 函数,这个init函数自动被go运行框架调用。写一个程序演示这个功能
# 演示代码如下: // main.go 文件内容: package main import ( a "go_dev/day02/example02_call_var02_init/add" "fmt" ) func main(){ // init 函数会在 main 函数执行之前先被调用 fmt.Println("Name=",a.Name) fmt.Println("Age=",a.Age) } // add.go 文件内容: package add import ( "fmt" ) var Name string = "xxxx" var Age int = 100 func init(){ // init 函数会在 main 函数执行之前先被调用 fmt.Println("initiated====>") Name = "hello world" Age = 10 } # 执行结果: [root@NEO example02_call_var02_init]# go run main/main.go initiated====> Name= hello world Age= 10 [root@NEO example02_call_var02_init]# // 执行顺序:1. 初始化全局变量; 2. 调用 init 函数 ;3. 调用 main 函数 # 导入多个包的示例如下: 目录结构如下: [root@NEO day02]# tree example02_call_var02_init example02_call_var02_init ├── add │ └── add.go ├── main │ └── main.go └── test └── test.go 3 directories, 3 files [root@NEO day02]# # 示例代码如下: # main.go 文件内容: package main import ( a "go_dev/day02/example02_call_var02_init/add" "fmt" ) func main(){ // init 函数会在 main 函数执行之前先被调用 fmt.Println("Name=",a.Name) fmt.Println("Age=",a.Age) } # add.go 文件内容: package add import ( _ "go_dev/day02/example02_call_var02_init/test" // 如果导入一个包只是为了初始化,并没有用到这个包中的变量或者函数,要在导入这个包时,在包的前面加上下划线 _ ;如果导入的这个包中的变量或者函数有被引用,则不需要加 _ "fmt" ) var Name string = "xxxx" var Age int = 100 func init(){ // init 函数会在 main 函数执行之前先被调用 fmt.Println("initiated====>") Name = "hello world" Age = 10 } # test.go 文件内容: package test import( "fmt" ) var Name string = "this is test pkg" // 变量的区分是包名 + 变量名,这个包中的 Name变量是 test.Name,和 add 包中的 Name变量是不同的变量 var Age int = 18 func init(){ fmt.Println("this is test initiated") fmt.Println("test.Name=",Name) fmt.Println("test.Age=",Age) Age = 20 fmt.Println("test.Age=",Age) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/test_multi_init go_dev/day02/example02_call_var02_init/main [root@NEO project]# bin/test_multi_init this is test initiated test.Name= this is test pkg test.Age= 18 test.Age= 20 initiated====> Name= hello world Age= 10 [root@NEO project]#
函数声明和注释
# 1. 函数声明: func (函数名) (参数列表) (返回值类型列表) {} # 2. 注释方式有两种: 单行注释: // 多行注释: /* */
常量
1. 常量使用 const 修饰,代表永远是只读的,不能修改 2. const 只能修饰 boolean ,number (int 相关类型、浮点型、complex)和 string 3. 语法: const identifier [type] = value ,其中 type 可以省略 举例如下: # 写法1: const b string = "hello world" const b = "hello world" const Pi = 3.1414926 const a = 9/3 # 错误示例: const c = getValue() // 常量不能这样通过函数直接赋值 # 写法2: const( a = 0 b = 1 c = 2 ) # 写法3: const( a = iota // a 是 0 b // 此时 a 后面的常量会自动 加 1,所以 b 为 1 c // c 为 2 )
示例5: 定义两个常量 Man=1 和 Female=2,获取当前时间的秒数,如果能被 Female 整除,则在终端打印 female,否则打印 man
// 获取当前秒数(时间戳): second := time.Now().Unix() # 示例代码如下: 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) // time.Millisecond 代表 1 毫秒, time.Microsecond 代表 1 微秒 } } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example02_const01 go_dev/day02/example02_const01/main [root@NEO project]# bin/example02_const01 man female man female man ^C [root@NEO project]#
变量
# 1. 语法: 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" )
示例6:写一个程序获取当前运行的操作系统名称和PATH环境变量的值,并打印在终端
# 示例代码如下: package main import( "fmt" "os" // os 是系统包 ) func main(){ // os.Getenv() 是获取系统变量 var goos string = os.Getenv("GOOS") fmt.Printf("The operating system is: %s\n",goos) var path = os.Getenv("PATH") fmt.Printf("Path is: %s\n",path) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example04_var01 go_dev/day02/example04_var01/main [root@NEO project]# bin/example04_var01 The operating system is: Path is: /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/go/bin:/root/bin [root@NEO project]#
值类型和引用类型
# 1. 值类型:变量直接存储值,内存通常在栈中分配 var i = 5 ===> i --> 5 在 golang中,基本数据类型 int , float, bool, string 以及 数组 和 struct 都是 值类型 在函数内部,假如修改了 值类型 变量的值,那么在函数外面,该变量的值不会改变 # 2. 引用类型:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配。通过 GC 回收 ref ---> 地址 ---> 值 在 golang 中,指针、 slice、 map、 chan等都是引用类型 在函数内部,假如修改了 引用类型 变量的值,那么在函数外面,该变量的值也会改变
示例7:写一个程序用来打印值类型和引用类型到终端,并观察输出结果
# 代码如下: package main import( "fmt" ) func modify_val(a int){ // a 是 int 类型的值类型变量 a = 10 // 值变量作为函数的形参传参时,该变量会 拷贝 一份传到函数中,在函数中对该变量修改时,修改的是拷贝的这个变量,不会影响函数外该变量原先的值 } func modify_cite(a *int){ // a 是 指针类型的引用类型; *数据类型 表示 指针 *a = 15 // *a 表示的是 指针a 所指向的那块内容中的值(即指针a实际的值) } func main(){ a := 5 b := make(chan int,1) fmt.Println("a=",a) fmt.Println("b=",b) modify_val(a) fmt.Println("value of int a after modify is:",a) // 值类型的变量 a 在函数内修改后不会改变函数外部 a 的值 modify_cite(&a) // &a 表示传入的是一个地址 fmt.Println("value of cite a is:",a) // 在 modify_cite 函数中修改 指针a指向地址指向的值后,函数外部该指针的值也会变 } # 编译运行后结果如下: [root@NEO project]# go build -o bin/example04_var02 go_dev/day02/example04_var02/main [root@NEO project]# bin/example04_var02 a= 5 b= 0xc000046070 # 引用类型的变量中存的是一个地址,这个地址指向的才是真正的存储内容的值 value of int a after modify is: 5 value of cite a is: 15 [root@NEO project]#
示例8:写一个程序,交换两个整数的值。比如: a=3; b=4; 交换之后: a=4; b=3
// 错误示例: package main import( "fmt" ) func swap(a int,b int){ tmp := a a = b b = tmp return } func main(){ first := 1 second := 2 swap(first, second) // 这种写法并不能在函数 swap 外实现 first 和 second 值的互换;对于 值类型的变量在函数传参时会对参数先 拷贝 一份,拷贝后是一个新的独立的内存地址,在函数内对这些变量修改时,是对 拷贝后的值(复本的值)进行修改,不会影响函数外这些变量的值 fmt.Println("first=",first) fmt.Println("second=",second) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example04_var03_err go_dev/day02/example04_var03_err/main [root@NEO project]# bin/example04_var03_err first= 1 # 两个变量的值未交换 second= 2 [root@NEO project]# // 正确示例: // 方式一: package main import( "fmt" ) func swap(a *int,b *int){ // *数据类型 表示 指针 tmp := *a // *a 表示的是 指针a 所指向的那块内容中的值(即指针a实际的值); a前面不加 * 就表示一个地址 *a = *b // 把 b指针实际的值 赋值给 a指针实际的值 *b = tmp // 把 tmp 变量对应的值 赋值给 b指针实际的值 return } func main(){ first := 1 second := 2 swap(&first, &second) // &first 表示传入的是一个地址 fmt.Println("first=",first) fmt.Println("second=",second) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example04_var0301 go_dev/day02/example04_var0301/main [root@NEO project]# bin/example04_var0301 first= 2 # 两个变量的交换 second= 1 [root@NEO project]# # 函数在传参时,会把变量先拷贝一份(复本),然后传参时传的都是变量的复本(包括传引用变量的地址时,也是先拷贝一份引用变量地址的复本,但源引用变量的地址和复本引用变量的地址指向的是同一块内存空间,即指向同一个值) // 方式二: package main import( "fmt" ) // 通过返回值的方式 func swap(a int,b int) (int, int){ return b,a } func main(){ first := 1 second := 2 first,second = swap(first, second) fmt.Println("first=",first) fmt.Println("second=",second) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example04_var0302 go_dev/day02/example04_var0302/main [root@NEO project]# bin/example04_var0302 first= 2 second= 1 [root@NEO project]# // 方式三: package main import( "fmt" ) func main(){ first := 1 second := 2 first, second = second, first // 变量直接互换 fmt.Println("first=",first) fmt.Println("second=",second) } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example04_var0303 go_dev/day02/example04_var0303/main [root@NEO project]# bin/example04_var0303 first= 2 second= 1 [root@NEO project]#
变量的作用域
1. 在函数内部声明的变量叫局部变量,生命周期仅限于函数内部 2. 在函数外部声明的变量叫全局变量,生命周期作用于整个包,如果是大写的,则可以在包外部访问 3. 声明在语句块({ })中的变量也是局部变量,其作用域也是在这个语句块({})中 // 示例代码1: package main import ( "fmt" ) var a = "G" // a 是 全局变量 func main(){ n() m() n() } func n(){ fmt.Println(a) } func m(){ a = "M" // 在函数内部对全局变量的值进行修改 fmt.Println(a) } # 运行结果如下: [root@NEO example04_var0304_scope]# go run main/main.go G M M [root@NEO example04_var0304_scope]# // 示例代码2: package main import ( "fmt" ) var a = "G" func main(){ n() m(a) // 传参时是先拷贝一份参数的复本,然后把参数的复本传进去 n() } func n(){ fmt.Println(a) } func m(a string){ a = "M" // 对复本进行修改 fmt.Println(a) } # 运行结果如下: [root@NEO example04_var0304_scope02]# go run main/main.go G M G [root@NEO example04_var0304_scope02]# // 示例代码3: package main import ( "fmt" ) var a string func main(){ a = "G" // 对全局变量a进行赋值 fmt.Println(a) f1() } func f1(){ a := "f1" // 此处声明(定义)了一个局部变量 a fmt.Println(a) f2() } func f2(){ fmt.Println(a) // f2 函数中没有变量 a ,会从全局变量中取 (f2 的作用域和f1的作用域没有关系) } // 运行结果如下: [root@NEO example04_var0304_scope03]# go run main/main.go G f1 G // f2 函数取的是全局变量的 a [root@NEO example04_var0304_scope03]# // 示例代码4: package main import ( "fmt" ) var a string func main(){ a = "G" // 对全局变量a进行赋值 fmt.Println(a) f1() } func f1(){ a := "f1" // 此处声明(定义)了一个局部变量 a fmt.Println(a) f2(a) } func f2(a string){ fmt.Println(a) } // 运行结果如下: [root@NEO example04_var0304_scope04]# go run main/main.go G f1 f1 [root@NEO example04_var0304_scope04]#
数据类型和操作符
1. bool 类型,只能存 true 和 false // 示例: var a bool var a bool = true var a = true 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(){ var a int var b int32 a = 15 b = b + 5 // 这行代码能正常编译 b = a + a // 这行代码会报错,因为 a 和 b 不是同一种类型的 int;解决方法是类型转换: 利用 int32(a) 把 a 也转换为 int32 类型 ---> b = int32(a) + int32(a) } 5. 字符类型: (byte 型, 或者叫 uint8 类型,代表了 ASCII 码的一个字符。) var a byte var a byte = 'c' // 字符 byte 只能用 单引号 // byte 是一个字符 6. 字符串类型: var str string // 字符串的两种表示方式: 1. 双引号 // 双引号会对字符串中的内容进行转义,如 \n 2. `` (反引号) // `` 不会对字符串中的内容进行转义处理,即 所见即所得;而且 反引号中的字符串能进行 换行 // 字符串的双引号和反引号示例代码如下: package main import( "fmt" ) func main(){ var str1 = "hello world \n\n" var str2 = `hello \n n \n this is a test string this is a test string too` fmt.Println("str1=",str1) fmt.Println("str2=",str2) var c byte = 'c' // c 的本质是一个数字:该字符在 ASCII 中对应的数字 fmt.Println("byte c =",c) fmt.Printf("%c\n",c) // 格式化输出中, %c 表示 输出单个字符 } // 编译之后的运行结果如下: [root@NEO project]# go build -o bin/example06_str go_dev/day02/example06_str/main [root@NEO project]# bin/example06_str str1= hello world str2= hello \n n \n this is a test string this is a test string too byte c = 99 c [root@NEO project]# # 补充: //在全局区域: var a int = 100 // 声明一个变量 a 为 int 类型,并初始化为 100;这一行代码放到 全局区域 是完全没问题的 // 但下面的代码放到全局区域则会报错: var a int // 声明变量 a 为 int ;这行代码没有错 a = 100 // 赋值操作 // 报错原因: 赋值操作也是在执行代码,但全局区域能声明变量,但不能执行代码,执行代码要放到函数中 逻辑操作符: == 、!= 、 < 、 <= 、 > 、 >= 数学操作符: + 、 - 、 * 、 / 、等等
示例9:使用 math/rand 生成10个随机整数,10个小于100的随机整数以及10个随机浮点数 (随机数)
// rand 包实现了伪随机数生成器; // 随机数由一个 Source 生成。像 Float64 和 Int 这样的顶级函数使用默认共享的 Source, 它会在每次程序运行时产生一系列确定的值。若每次运行都需要不同的行为,需使用 Seed 函数来初始化默认的 Source。 // 随机数由一个 Source 生成;使用 Seed 函数来初始化默认的 Source // 示例代码: package main import ( "fmt" "math/rand" "time" ) func init(){ rand.Seed(time.Now().UnixNano()) // 利用当前纳秒时间戳 设置 种子 } func main(){ for i :=0;i<10;i++{ a := rand.Int() // Int() returns a non-negative pseudo-random int from the default Source. fmt.Println(a) } for i :=0;i<10;i++ { a := rand.Intn(100) // Intn(n) returns, as an int, a non-negative pseudo-random number in [0,n) from the default Source. It panics if n <= 0. fmt.Println(a) } for i :=0;i<10;i++ { a := rand.Float32() // Float32() returns, as a float32, a pseudo-random number in [0.0,1.0) from the default Source. fmt.Println(a) } } # 编译之后的运行结果如下: [root@NEO project]# go build -o bin/example05_rand go_dev/day02/example05_rand/main [root@NEO project]# bin/example05_rand 6490486822796045968 9144196389670471465 6917284983782713832 6708046869565845710 6979317820484376884 7814095851982680867 8062397337643966196 6169353628608003521 6637280047299576259 8924964441859908871 73 96 8 89 25 72 86 27 53 42 0.87334687 0.34517118 0.82716334 0.49546078 0.74162775 0.4064283 0.72067785 0.277449 0.95820624 0.4750664 [root@NEO project]#
格式化输出:
// 示例代码: package main import( "fmt" ) func main(){ var a int var b bool var c byte = 'c' d := 'd' // d 是 byte 类型 fmt.Printf("%v\n",a) // 相应值的默认格式。在打印结构体时,“加号”标记(%+v)会添加字段名 fmt.Printf("%v\n",b) // %v 就是以原样打印出来(不知道怎么输出时就用 %v) fmt.Printf("%v\n",c) fmt.Printf("%+v\n",a) // 在打印结构体时,“加号”标记(%+v)会添加字段名 fmt.Printf("%#v\n",b) // %#v ---> 相应值的Go语法表示 fmt.Printf("%#v\n",c) fmt.Printf("%T\n",b) // %T ---> 相应值的类型 fmt.Printf("%T\n",d) fmt.Printf("90%%\n") // %% ---> 字面上的百分号,并非值的占位符(转义) fmt.Printf("%t\n",b) // %t ---> 单词 true 或 false。(bool 型) fmt.Printf("%b\n",100) // %b ---> 打印其二进制 fmt.Printf("%f\n", 199.22) // %f ---> 浮点型 fmt.Printf("%q\n", "this is a test") // 双引号围绕的字符串,由Go语法安全地转义 (带双引号的字符串) fmt.Printf("%x\n", 39839333) // %x ---> 十六进制(十六进制,小写字母,每字节两个字符) fmt.Printf("%p\n", &a) // %p ---> 十六进制表示,前缀 0x ; &a 表示其地址 a = 100 str := fmt.Sprintf("a=%d", a) // Sprintf ---> S 表示返回字符串 fmt.Printf("%q\n", str) } // 编译之后的运行结果如下: [root@NEO project]# go build -o bin/example07_fmt go_dev/day02/example07_fmt [root@NEO project]# bin/example07_fmt 0 false 99 0 false 0x63 bool int32 90% false 1100100 199.220000 "this is a test" 25fe665 0xc0000120a8 "a=100" [root@NEO project]#
字符串操作:
// 示例代码: package main import( "fmt" ) func reverse(str string) string { // 字符串反转函数 var result string strLen := len(str) for i:=0; i<strLen; i++ { result = result + fmt.Sprintf("%c",str[strLen-1-i]) } return result } func main(){ var str1 = "hello" str2 := "world" // 字符串拼接 str3 := str1 + " " + str2 fmt.Println(str3) str4 := fmt.Sprintf("%s %s",str1,str2) fmt.Println(str4) n := len(str4) // 字符串长度 fmt.Printf("length of str4 is %d\n",n) substr := str4[0:5] // 字符串截取 // 同一个变量只能声明一次 fmt.Println(substr) result := reverse(str3) fmt.Println(result) } // 编译后执行结果如下: [root@NEO project]# go build -o bin/example06_str02 go_dev/day02/example06_str02/main [root@NEO project]# bin/example06_str02 hello world hello world length of str4 is 11 hello dlrow olleh [root@NEO project]#
示例: 打印出 100~999 中所有的 “水仙花数”,水仙花数 是指一个三位数,其各位数字立方和等于该数本身。例如: 153 = 1**3 + 5**3 + 3**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 n == sum } func main(){ for i:=100; i <= 999; i++ { if isNumber(i) == true{ fmt.Println(i,"is shuixianhuashu") } else { fmt.Println(i,"not shuixianhuashu") } } } # 编译后执行结果如下: [root@NEO project]# go build -o bin/example08_square go_dev/day02/example08_square/main [root@NEO project]# bin/example08_square |grep is 153 is shuixianhuashu 370 is shuixianhuashu 371 is shuixianhuashu 407 is shuixianhuashu [root@NEO project]# // fmt.Scanf() ---> 读取终端输入,如: fmt.Scanf("%d",&num) ---> 内部如果需要改 num 的值,就传入这个参数的地址;如果不需要修改 num 的值,直接传num 这个变量就行
fmt.Scanf() 示例:
package main import ( "fmt" ) func main (){ var s string fmt.Printf("default:%s\n",s) fmt.Scanf("%s\n",s) fmt.Printf("s without &:%s\n",s) fmt.Scanf("%s\n",&s) fmt.Printf("s with &:%s",s) }
运行结果如下:
[root@NEO ~]# go run main.go default: a s without &: s with &:[root@NEO ~]# [root@NEO ~]# go run main.go default: a b s without &: s with &:b [root@NEO ~]# // 所以在用 fmt.Scanf() 接收外部变量时, 传入的变量前要加上 &