关键字、标识符
标识符:
是用户或系统定义的有意义单词组合,或单词与数字组合(具体意义有定义者决定)
标识符以字母下划线开头,大小写敏感,比如:boy, Boy, _boy, _(匿名变量,用来忽略结果)
标识符命名规范:在习惯上,Go语言程序员推荐使用驼峰式命名,当名字有几个单词组成的时优先使用大小写分隔,而不是优先用下划线分隔。因此,在标准库有QuoteRuneToASCII和parseRequestLine这样的函数命名,但是一般不会用quote_rune_to_ASCII和parse_request_line这样的命名。而像ASCII和HTML这样的缩略词则避免使用大小写混合的写法,它们可能被称为htmlEscape、HTMLEscape或escapeHTML,但不会是escapeHtml。
关键字:
是 Go 语言提供的有特殊含义的符号,也叫做“保留字”
系统保留关键字:
break | default | func | interface | select |
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthough | if | range | type |
continue | for | import | return | var |
常量与变量
常量
常量使用 const 修饰,表示是只读的,不能修改
const 只能修饰 boolean,number(int相关,浮点数,complex)和 string 类型
语法:const identifier [type] = value(type 可省略)
优雅写法:
const(
name string = "skier"
age int = 10
salary int = 5000 / 2
// gender boolean = getGender() // const不能从函数中获取
)
常量因为在编译期确定,所以可以用于数组声明:
const size = 4
var arrayA [size]int
变量
声明一个变量:var identifier [type]
// 声明变量并赋值
var a int = 100
// 简写(自动推导类型)
a := 100
优雅写法:
var (
name string = "johny"
age int = 10
)
默认值:
- int 0
- float 0.0,编译默认推导为 float64
- bool false
- string ""
- slice,函数,指针变量默认为 nil (slice 默认为 nil,但打印输出是 [],可使用 == nil 进行判空)
作用域:从定义变量的代码行开始,一直到直接所属的大括号结束为止(全局变量除外)
- 在函数内部声明的变量叫做局部变量,生命周期仅限于函数内部
- 在函数外部声明的变量叫做全局变量,生命周期作用于整个包,如果首字母大写的话,则可以被其它包导入
在编程中,变量在其实现了变量的功能后,作用范围越小,所造成的问题可能性越小,每一个变量代表一个状态,有状态的地方,状态就会被修改,函数的局部变量只会影响一个函数的执行,但全局变量可能会影响所有代码的执行状态,因此限制变量的作用范围对代码的稳定性有很大的帮助
基本数据类型与操作符
数字类型:int8, int16, int32, int64, uint8, uint16, uint32, uint64
bool 类型:ture, false (bool 型无法参与数值运算,也无法与其他类型进行转换。)
浮点类型:float32, float64
字符类型:byte
字符串类型:字符串实现基于 UTF-8 编码
"" 双引号,定义单行字符串
`` 反引号,定义多行字符串(在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会原样输出)
多行字符串一般用于内嵌源码和内嵌数据等
类型转换
格式:type(variable)
var a int = 8
var b int32 = int32(a)
浮点数转换成 int 类型,精度会丢失
var c float32 = math.Pi
fmt.Println(int(c))
int32 转换成 int16,数据会截断
// 初始化一个32位整型值
var a int32 = 1047483647
// 输出变量的十六进制形式和十进制值
fmt.Printf("int32: 0x%x %d\n", a, a)
// 将a变量数值转换为十六进制, 发生数值截断
b := int16(a)
// 输出变量的十六进制形式和十进制值
fmt.Printf("int16: 0x%x %d\n", b, b)
结果是:
int32: 0x3e6f54ff 1047483647
int16: 0x54ff 21759
字符串转义符
转移符 | 含 义 |
---|---|
\r | 回车符(返回行首) |
\n | 换行符(直接跳到下一行的同列位置) |
\t | 制表符 |
\' | 单引号 |
\" | 双引号 |
\\ | 反斜杠 |
操作符
数字操作符:+, -, *, /, %
比较运算符:>, >=, <, <=, ==, !=
字符串操作
拼接:
var str1 string = "hello"
var str2 string = "world"
var str string = str1 + str2
// var str string = fmt.Sprintf("%s%s", str1, str2)
值类型与引用类型
值类型
本质上是原始类型,变量直接储存值,内存通常在栈中分配,包括 int, float, bool, string 以及数组和 struct(结构体)
对值类型进行操作,一般都会返回一个新创建的值,所以把这些值传递给函数时,其实传递的是一个值的副本
func main() {
name:="张三"
fmt.Println(modify(name))
fmt.Println(name)
}
func modify(s string) string{
s=s+s
return s
}
//Output
张三张三
张三
以上是一个操作字符串的例子,通过打印的结果,可以看到,本来 name 的值并没有改变,也就是说,我们传递的是一个副本,并且返回一个新创建的字符串
基本类型因为是值的拷贝,并且在对他进行操作的时候,生成的也是新创建的值,所以这些类型在多线程里是安全的,我们不用担心一个线程的修改影响了另外一个线程的数据
引用类型
引用类型与值类型恰恰相反,它的修改可以影响到任何引用到它的变量;变量存储的是地址,这个地址存储最终的值,通常在堆内存上分配,通过 GC 回收,包括 指针,select,map,chan 等
引用类型之所以可以引用,是因为我们创建引用类型的变量,其实是一个标头值,标头值里包含一个指针,指向底层的数据结构,当我们在函数中传递引用类型时,其实传递的是这个标头值的副本,它所指向的底层结构并没有被复制传递,这也是引用类型传递高效的原因。
流程控制
if else 分支判断
if condition1 {
block1
} else if condition2 {
block2
} else {
block3
}
switch case 语句
func main(){
var variabel string = "a"
switch variabel {
case "a", "b":
fmt.Println(variabel)
// fallthrough // 会执行下一个case的语句块
case "c":
fmt.Println(variabel)
default:
fmt.Println("default output")
}
}
case 后边的值可以写多个,是 或 的关系
case 语句块末尾如果加上 fallthrough,会接着执行下一个 case 的语句块
for 循环
for i:=0; i<100 ; i++ {
fmt.Println("hello, world~")
}
死循环(for)
func main(){
for {
fmt.Println("hello, world~")
time.Sleep(time.Second)
}
}
加判断的 for 循环
func main(){
var i int
for i<100 {
fmt.Println(i)
i += 1
}
}
for range 语句
可以使用 for range 遍历数组、切片、字符串、map 及通道(channel)。通过 for range 遍历的返回值有一定的规律:
- 数组、切片、字符串返回索引和值
- map 返回键和值
- 通道(channel)只返回通道内的值
遍历数组:
var arrayA = [3]string{"hammer", "soldier", "mum"}
for index, value := range arrayA {
fmt.Println(index, value)
}
运行结果:
0 hammer
1 soldier
2 mum
用匿名标识符忽略 index
var arrayA = [3]string{"hammer", "soldier", "mum"}
for _, value := range arrayA {
fmt.Println(value)
}
运行结果:
hammer
soldier
mum
当然,for 循环中也能够支持:break, continue
goto 语句
可以通过标签进行代码间的无条件跳转,goto语句可以快速跳出循环 或 实现同样的逻辑 有一定的帮助:
快速跳出循环:
func main(){
for i:=0; i<=100; i++{
for j:=0; j<=100; j++{
if j == 10 {
// 直接跳转到标签
goto breakHere
}
}
}
breakHere:
fmt.Println("hello world~")
}
运行结果:
hello world~
使用 goto 集中处理错误:
err := firstCheckError()
if err != nil {
goto onExit
}
err = secondCheckError()
if err != nil {
goto onExit
}
fmt.Println("done")
return
onExit:
fmt.Println(err)
exitProcess()
函数
Go 语言支持普通函数、匿名函数和闭包
普通函数声明:func 函数名(参数列表) (返回值列表) {函数体}
不支持重载,一个源文件内不能有两个相同名称的函数
函数是一等公民,也是一种类型,可以赋值给变量
函数的传参方式:
- 值传递 (基本数据类型都是值传递)
- 引用传递 (指针,slice,map,chan,interface)
注意:无论是值传递还是引用传递,传递给函数的都是变量的副本,不过,值传递是对值的拷贝,引用传递是地址的拷贝,一般来说,地址拷贝更为高效,而值拷贝取决于拷贝对象的大小,对象越大,则性能越低
返回值命名:
返回值不需要定义,直接使用(命名的返回值变量的默认值为类型的默认值,即数值为 0,字符串为 "",布尔为 false、指针为 nil)
func calc(a int, b int) (c int) {
c = a + b
return c
}
可变长参数:
可变参数变量是一个包含所有参数的切片
func calc(a int, b int, arg... int) {
fmt.Println(arg[0])
}
defer 的用途:
- 延迟调用是在 defer 所在函数结束时进行,函数结束可以是正常返回时,也可以是发生宕机时
- 多个 defer 语句,按先进后出(栈)的顺序执行
- defer 语句中的变量,在 defer 声明时就决定了
- defer 语句正好是在函数退出时执行的语句,所以使用 defer 能非常方便地处理资源释放问题
关闭文件句柄
注意:不能将这一句代码放在第3行空行处,一旦文件打开错误,f将为空,在延迟语句触发时,将触发宕机错误
func read(){
file err := open(filename)
if err != nil{
return
}
defer file.Close()
}
锁资源的释放
func lock(){
mc.Lock()
defer mc.Unlock()
}
数据库连接的释放
func connect(){
conn := openDatabase()
defer conn.Close()
}
调用函数
函数在定义后,可以通过调用的方式,让当前代码跳转到被调用的函数中进行执行。调用前的函数局部变量都会被保存起来不会丢失;被调用的函数结束后,恢复到被调用函数的下一行继续执行代码,之前的局部变量也能继续访问
递归函数
一个函数在内部调用自己,就叫做递归,下面来举两个递归函数的Demo
递归求阶乘
func calc(n int) int {
if n <= 1{
return 1
}
return calc(n-1) * n
}
func main(){
result := calc(5)
fmt.Println(result)
}
运行结果:
120
斐波拉契数
func fab(n int) int {
if n<=1{
return 1
}
return fab(n-1) + fab(n-2)
}
func main(){
var n int = 6
var fabCount int
for i:=0; i<=n; i++{
fabCount += fab(i)
}
fmt.Println(fabCount)
}
运行结果:
33
匿名函数
匿名函数没有函数名,只有函数体,可以直接被当做一种类型赋值给函数类型的变量,匿名函数也往往以变量的方式被传递
匿名函数经常被用于实现回调函数、闭包
定义一个匿名函数:
func(str string){
fmt.Println("hello", str)
}("world")
运行结果:
hello world
也可以将匿名函数赋值给变量:
f := func(str string){
fmt.Println("hello", str)
}
f("world")
匿名函数当做参数:
func visit(sliceA []int, f func(int)){
for _, value := range sliceA {
f(value)
}
}
func main(){
var sliceA []int = []int{1,2,3,4,5}
f := func(a int){
fmt.Print(a)
}
visit(sliceA, f)
}
运行结果:
12345
使用匿名函数实现操作封装:
func main(){
var mapA map[string]func()
mapA = map[string]func(){
"fire": func(){
fmt.Println("chicken fire")
},
"run": func(){
fmt.Println("soldier run")
},
"fly": func(){
fmt.Println("angel fly")
},
}
// fmt.Println(mapA)
// 接收命令行参数,key,默认值,帮助
var skill *string = flag.String("skill", "", "skill type")
flag.Parse()
f, err := mapA[*skill]
if err == true {
f()
} else {
fmt.Println("skill not fount")
}
}
运行效果:
$ go run main.go --skill=fly
angel fly
$ go run main.go --skill=run
soldier run
闭包(Closure)
闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使已经离开了自由变量的环境 也不会被释放或者删除,在闭包中可以继续使用这个自由变量(闭包(Closure)在某些编程语言中也被称为 Lambda 表达式)
简单的说:
闭包 = 函数 + 引用环境
同一个函数与不同的引用环境组合,可以形成不同的实例,如图:
实现一个简单的闭包:
func closureFunc(str string) func(){
wapper := func(){
fmt.Println("hello", str)
}
return wapper
}
func main(){
f := closureFunc("world~")
// 调用闭包函数
f()
}
运行结果:
hello world~
累加器的实现(闭包的记忆效应)
func accumulate(num int) func() int {
wapper := func() int {
num += 1
return num
}
return wapper
}
func main(){
accumulator := accumulate(10)
ret1 := accumulator()
fmt.Println(ret1)
ret2 := accumulator()
fmt.Println(ret2)
}
运行结果:
11
12
go 基本程序结构
// 任何一个代码源文件隶属于一个包
package main
//import 关键字,同一个包内的函数可以直接调用,不同包中的函数通过 包名.函数名 的方式调用
import (
"fmt"
"/go_dev/test"
)
// 初始化函数
func init(){
fmt.Println("执行初始化操作")
}
// main 程序入口
func main(){
fmt.Println("hello, world~")
}
init 函数
每个源文件都可以包含一个 init 函数,会自动被编译器执行
包访问控制规则
-
- 包内函数名首字母大写表示此函数/变量是可导出的
- 小写表示此函数/变量是私有的,在包外部不能访问
程序执行顺序(栈) *
- 导入其它包,初始化 被导入包 内的全局变量,执行 被导入包 内所有源文件的 init 函数
- 初始化 main 包内全局变量
- 调用 main 包 init 函数
- 执行 main 函数
练习1:写一个函数,对于一个整数n,求出所有两两相加等于n的组合,比如 n=5
package main
import (
"fmt"
)
func calc(n int){
for i:=0; i<=n; i++ {
num := n - i
fmt.Printf("%d+%d=%d\n", i, num, n)
}
}
func main(){
calc(5)
}
结果:
0+5=5
1+4=5
2+3=5
3+2=5
4+1=5
5+0=5
练习2:一个程序包含 add 和 main 两个包,add 包中有两个变量 Name 和 age,在 main 中访问 Name 和 age 变量,打印输出
首先在 go_dev 下新建一个目录 example(go_dev 目录在环境变量 GOPATH 下 src 目录,go编译器根据系统路径会找到 go_dev 目录下的包)
add包下写 add.go
package add
var Name string = "skier"
var age int = 19
main包下写 main.go
package main
import (
"go_dev/example/add"
// a "go_dev/example/add" // 包别名的应用
"fmt"
)
func main(){
fmt.Println(add.Name)
fmt.Println(add.age) // 不能访问
}
作业
1.判断101~200之间有多少个素数,并输出打印
package main
import (
"fmt"
)
func main(){
for i:=101; i<=200; i++{
var flag bool = true
for j:=2; j<i; j++{
if (i % j == 0) {
flag = false
}
}
if (flag == true){
fmt.Println("素数i:", i)
}
}
}
2.打印出101~999中所有的水仙花数,所谓水仙花数是指一个三位数,其各位数字的立方和等于该数本身,例如153是一个水仙花数,1*1*1 + 5*5*5 + 3*3*3 = 153
package main
import (
"fmt"
)
func narcissisticNum(num int) bool {
var g int = num % 100 % 10
var s int = num % 100 / 10
var b int = num / 100
//fmt.Println(g, s, b)
var cube int = g*g*g + s*s*s + b*b*b
return num == cube
}
func main(){
for i:=100; i<=999; i++{
result := narcissisticNum(i)
if result == true {
fmt.Println("水仙花数是:", i)
}
}
}
3.对于一个数n,求n的阶乘之和,即:1! + 2! + ... + n!
package main
import (
"fmt"
)
func summation(n int) int {
var result int
for i:=1; i<=n; i++ {
var tmpe int = 1
for j:=1; j<=i; j++{
tmpe *= j
}
result += tmpe
}
return result
}
func main(){
var num int = 5
var result int = summation(num)
fmt.Println("sum result:", result)
}
4.在终端打印 9*9 乘法表
package main
import(
"fmt"
)
func multiplication(){
for column:=1; column<=9; column++{
for row:=1; row<=column; row++{
fmt.Printf("%d*%d=%d\t", column, row, column*row)
}
fmt.Println()
}
}
func main(){
multiplication()
}
5.一个数如果恰好等于它的因子之和,这个数就称之为“完数”,例如:1+2+3=6,在终端输出1000以内的所有完数
package main
import(
"fmt"
)
func showPerfect(n int){
for i:=1; i<n; i++{
var sum int
for j:=1; j<i; j++{
if i % j == 0{
sum += j
}
}
if sum == i{
fmt.Printf("完数:%d \n", i)
}
}
}
func main(){
const num int = 1000
showPerfect(num)
}
6.输入一个字符串,判断其是否为“回文”,回文字符串是指从左到右读,与从右到左读是完全相同的字符串
package main
import (
"fmt"
)
func isReverse(str string) bool {
// 转为字符
char := []rune(str)
var length int = len(char)
for i:=0; i<=length / 2 - 1; i++{
if char[i] != char[length-1-i]{
return false
}
}
return true
}
func main(){
var input string
fmt.Scanf("%s", &input)
result := isReverse(input)
fmt.Println("result:", result)
}
7.输出一行字符串,分别统计其中英文字母,空格,数字和其它符号的个数
package main
import (
"fmt"
"bufio"
"os"
)
func count(str string) (wordCount, spaceCount, numCount, otherCount int) {
chars := []rune(str)
for _, value := range chars{
switch {
case value >= 'a' && value <= 'z':
wordCount += 1
case value >= 'A' && value <= 'Z':
wordCount += 1
case value == ' ':
spaceCount += 1
case value >= '0' && value <= '9':
numCount += 1
default:
otherCount += 1
}
}
return
}
func main(){
reader := bufio.NewReader(os.Stdin)
// 读取一行的内容
result, _, error := reader.ReadLine()
if error == nil{
wordCount, spaceCount, numCount, otherCount := count(string(result))
fmt.Printf("wordCount:%d\nspaceCount:%d\nnumCount:%d\notherCount:%d\n", wordCount, spaceCount, numCount, otherCount)
}
}
8.计算两个大数相加的和,这两个大数会超过 int64 的表示范围
来源:oschina
链接:https://my.oschina.net/u/4276314/blog/3532871