golang很特别:
- 没有对象,没有继承多态,没有泛型,没有try/catch
- 有接口,函数式编程,CSP并发模型(gorouting + channel)
go version:查看版本
go env:查看环境配置
安装好go后,打开go moudle,修改国内镜像配置:
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.cn,direct
安装goimports:
go get -v golang.org/x/tools/cmd/goimports
然后进入vscode,新建一个go文件,vscode会弹出两个窗口,都选择安装即可。
基本语法
变量
golang定义完变量后是有默认初值的,不像C是不确定值,也不像java是null。
package main
import (
"fmt"
)
// 同时定义多个变量
var (
aa = 3
bb = "kkk"
cc = true
)
func variableDefine() {
var a, b int;
var c string;
var d, e, f = 1, "abc", true // 类型推断
g := "short" // 缩写(不能用在函数外)
fmt.Printf("%d %d %q\n", a, b, c) // %q可以打出引号
fmt.Println(d, e, f, g)
}
func main() {
variableDefine()
}
内建变量类型
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
- byte, rune
- float32, float64, complex64, complex128
int后没数字就是根据操作系统是32位还是64位决定。go中没有long类型,需要长一点就用int64。ptr就是指针,比C的指针要方便好用很多。
rune是字符类型,因为char只有一字节,很多编码是超出一字节的(如一个汉字两字节),会造成很多坑。因此采用rune,32位4字节。byte依然是8位。
go中也没有double类型。
complex64的实部和虚部都是float32,complex128的实部和虚部都是float64。
强制类型转换
golang中只有强制类型转换,没有隐式类型转换。
func main() {
var a, b = 3, 4
var c int = int(math.Sqrt(float64(a * a + b * b)))
fmt.Println(c)
}
上面的代码中,math.Sqrt接收一个float64的参数,返回一个float64的值。因此必须对输入的int类型做强制类型转换,计算完后赋给int类型的c时也需要强制类型转换,如果算出来的不是5,而是4.99等,强制类型转换后值会变成4,要特别注意处理这种情况。
常量
类似C中的常量,是做一个值的替换,但是不用像C中一样全部大写。不类似js中的const。
func consts() {
const filename string = "abc.txt"
const a, b = 3, 4
var c int = int(math.Sqrt(a*a + b*b))
fmt.Println(filename, c)
}
func main() {
consts()
}
常量可以指定类型,也可以不指定,不指定的时候类型是不确定的,const不指定类型的数字可以作为任意数字类型属于。像上面代码中,a和b不指定类型的话,传入math.Sqrt时就可以不做强制类型转换。
枚举
go语言没有枚举关键字,一般就用一组const来表示。
func main() {
const(
cpp = 0
java = 1
python = 2
golang = 3
)
fmt.Println(cpp, java, python, golang) // 0 1 2 3
}
go为自增的const设计了一种简便写法:
func main() {
const(
cpp = iota
java
python
golang
)
fmt.Println(cpp, java, python, golang) // 0 1 2 3
}
// 跳过某些值
func main() {
const(
cpp = iota
_
python
golang
javascript
)
fmt.Println(cpp, javascript, python, golang) // 0 4 2 3
}
// iota是一种表达式
func main() {
const(
b = 1 << (10 * iota)
kb
mb
gb
tb
pb
)
fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
}
判断、选择、循环
if
判断条件不需要加括号,其他和C、js差不多。
func main() {
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}
// 另一种写法
func main() {
const filename = "abc.txt"
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", contents)
}
}
- if语句的条件里可以赋值
- if语句的条件里赋值的变量作用域就在这个if语句里面,如上面第二种写法contents只能在if-else里访问。
switch
switch 会自动break,除非使用 fallthrough。panic 会中断程序执行,输出错误信息。
func eval(a, b int, op string) int {
var result int
switch op {
case "+":
result = a + b
case "-":
result = a - b
case "*":
result = a * b
case "/":
result = a / b
default:
panic("unsupported operator:" + op)
}
return result
}
// switch后面可以不跟表达式
func grade(score int) string {
g := ""
switch {
case score < 0 || score > 100:
panic(fmt.Sprintf("Wrong score: %d", score))
case score < 60:
g = "F"
case score < 80:
g = "C"
case score < 90:
g = "B"
case score <= 100:
g = "A"
}
return g
}
func main() {
fmt.Println(eval(2, 3, "+"))
fmt.Println(grade(59))
fmt.Println(grade(79))
fmt.Println(grade(89))
fmt.Println(grade(100))
fmt.Println(grade(101))
}
输出:
5
F
C
B
A
panic: Wrong score: 101
goroutine 1 [running]:
main.grade(0x65, 0xc00000e018, 0xc000068f28)
/Users/pengwei/Desktop/golearn/hello.go:26 +0x108
main.main()
/Users/pengwei/Desktop/golearn/hello.go:45 +0x2e8
exit status 2
函数定义
// 单个返回值
func func_name(a, b int) int { //同类型,可以省略 a, b int
return a + b
}
// 多个返回值
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
命名返回值:可以给一个函数的返回值指定名字。如果指定了一个返回值的名字,则可以视为在该函数的第一行中定义了该名字的变量。
func rectProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = (length + width) * 2
return // no explicit return value
}
func main() {
area, perimeter := rectProps(10.8, 5.6)
fmt.Printf("Area %f Perimeter %f", area, perimeter)
}
多返回值常用于返回错误:
func eval(a, b int, op string) (int, error) {
switch op {
case "+":
return a + b, nil;
case "-":
return a - b, nil;
default:
return 0, fmt.Errorf("unsupported operation: " + op)
}
}
func main() {
fmt.Println(eval(3, 4, "x"))
}
函数还可以作为参数传入另一个函数:
func apply(op func(int, int) int, a, b int) int {
p := reflect.ValueOf(op).Pointer()
opName := runtime.FuncForPC(p).Name()
fmt.Printf("Calling function %s with args " + "(%d, %d)\n", opName, a, b)
return op(a, b)
}
func main() {
fmt.Println(apply(func(a int, b int) int {
return int (math.Pow(float64(a), float64(b)))
}, 3, 4))
}
不定参数:
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
}
指针、数组、容器
指针
func main() {
fmt.Println(sum(1, 2, 3, 4, 5))
var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)
}
golang中的指针比C中的指针简单,体现在golang中的指针不能进行运算。
值传递和引用传递
C中参数的传递分为值传递和引用传递:
void pass_by_val(int a) {
a++;
}
void pass_by_ref(int& a) {
a++;
}
int main() {
int a = 3;
pass_by_val(a);
printf("After pass_by_val: %d\n", a); // 3
pass_by_ref(a);
printf("After pass_by_ref: %d\n", a); // 4
golang中只有值传递。可以使用指针实现引用传递。
func swap(a, b *int) {
*b, *a = *a, *b
}
func main() {
a, b := 3, 4
swap(&a, &b)
fmt.Println(a, b) // 4, 3
}
更简单的方式实现交换:
func swap(a, b int) (int, int) {
return b, a
}
func main() {
a, b := 3, 4
a, b = swap(a, b)
fmt.Println(a, b) // 4, 3
}
数组
func main2() {
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10}
var grid [4][5]bool
fmt.Println(arr1, arr2, arr3)
fmt.Println(grid)
// for i := 0; i < len(arr3); i++ {
// fmt.Println(arr3[i])
// }
// 遍历数组用range更方便
for i, v := range arr3 {
fmt.Println(i, v)
}
}
golang中的数组是值类型,而不是引用类型。
[3]int和[5]int是不同类型- 调用
func f(arr [3]int)会拷贝数组
func printArray(arr [5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int
// arr2 := [3]int{1, 3, 5} // 无法传入printArray,因为[3]int和[5]int不是同一类型
arr3 := [...]int{2, 4, 6, 8, 10}
printArray(arr1)
printArray(arr3)
fmt.Println(arr1)
fmt.Println(arr3)
}
// 结果
0 100
1 0
2 0
3 0
4 0
0 100
1 4
2 6
3 8
4 10
[0 0 0 0 0]
[2 4 6 8 10]
如果传的是数组的指针就可以修改数组了:
func printArray(arr *[5]int) {
arr[0] = 100
for i, v := range arr {
fmt.Println(i, v)
}
}
func main() {
var arr1 [5]int
// arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6, 8, 10}
printArray(&arr1)
printArray(&arr3)
fmt.Println(arr1)
fmt.Println(arr3)
}
// 结果
0 100
1 0
2 0
3 0
4 0
0 100
1 4
2 6
3 8
4 10
[100 0 0 0 0]
[100 4 6 8 10]
这个感觉操作数组很麻烦,但其实golang中很少直接操作数组,一般都是操作数组的切片。