1. 变量
变量的声明有四种方式:
var a intvar a int = 100var a = 100:=a := 100
a := 100
多个变量一起声明的写法:
var (
a int = 100
b string = "abc"
)
匿名变量
_
go支持函数多返回值,而当我们对于某个函数的返回值是不关心的时候,可以使用匿名变量来接收
fd, _ := os.Open(xxx)_
2. 常量
const
varconst:=
比如:
const a int = 100
const (
a = 10
b = 20
)
iota
iotaconst
iotaconst
const (
RED = iota
BLUE
BLACK
....
)
constiota
RED=0,BLUE=1,BLACK=2
RED=5 * 0=0,BLUE=5 * 1=5,BLACK=5 * 2=10
iota
iota
const (
_ = iota // 赋值给_忽略这个值
B = 1 << (10 * iota)
KB
MB
GB
TB
...
)
4.函数
go函数是允许有多个返回值的。go的函数定义可以有以下几种写法:
func test(a string, b int) (int, int) {
....
return 100, 200
}
func test(a string, b int) (c, d int) {
...
c = 100
d = 200
return
}
5. init函数
init函数是go在每个包初始化后自动执行的,而且在main函数之前执行
因此,init函数常用来:对变量初始化,注册等。
init函数的几个特点:
package xxxxmain
package main
import "fmt"
func int() {
fmt.Println("init ok")
}
func main() {
fmt.Println("main...")
}
6. import 导包
import
import _ "fmt"_init()import aa "fmtaa.Println()import . "fmt"
7. defer
defer关键字是go独有的,是一种延迟语句,在函数return前执行defer。
一个函数中可以添加多个defer语句,执行顺序是逆序的,先定义的defer最后执行
一般defer用于资源的关闭操作比较多。
结论就是:return最先执行,return负责将结果写入返回值中;接着defer开始执行一些收尾工作;最后函数携带当前返回值退出。
8. 数组
func add(array [4]int) {
fmt.Println(array[0], array[1], array[2], array[3])
}
func main() {
arr := [5]int{1, 2, 3, 4}
add(arr)
}
9. 数组切片(slice)
数组切片slice,也叫动态数组。
创建数组切片有两种方式:基于数组和直接创建
func main() {
// 先定义一个数组
var myArray [10]int = [10]int{1,2,3,4,5,6,7,8,9,10}
// 基于数组创建一个数组切片
var mySlice []int = myArray[:5]
}
元素的遍历
for i := 0; i < len(mySlice); i++ {
....
}
for i, v := range mySlice {
....
}// i 是index v是元素值
动态增减元素:
mySlice2 := []int{8, 9, 10}
mySlice = append(mySlice, mySlice2...)
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{6, 7, 8}
copy(slice2, slice2) // 只会复制slice1的前三个元素到slice2中
// slice2 = {1,2,3} slice1 = {1,2,3,4,5}
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
// slice2 = {6,7,8} slice1 = {6,7,8,4,5}
func printArray(myArray []int) {
...
}
10. map
var myMap map[int]string
myMap = make(map[int]string, 10)
myMap[0] = "java"
myMap[1] = "Go"
myMap := make(map[int]string)
myMap[0] = "java"
myMap[1] = "Go"
myMap := map[int]string{
0: "java",
1: "Go",
}
value, ok := myMap[1]
if ok { // 找到了
....
}
11. 面向对象
我们都知道面向对象三个特点:封装,继承,多态。
但是go中并不像其他面向对象语言那样有很多的概念,go语言的面向对象编程是基于语言类型系统的,整个类型系统通过接口串联。
1. 类型系统
go语言中的类型是可以添加方法的,可以给任何类型,包括内置类型增加新方法。比如:
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
// 可以这样使用
func main() {
var a Integer = 1
if a.Less(2) {
fmt.Println(a, "Less 2")
}
}
typeInteger
新增方法这个语法可以以java类的概念来理解为:Integer就是一个类,而a就相当于类中的this,而Less是类里的一个方法,当然a就可以调用到类里的成员了,但是这里的类实质是一个int,所以也就成员变量就是自身int值变量了,但如果a是一个结构体那就有成员变量了。
func (a *Integer) Add(b Integer) {
*a += b
}
其实上面用指针和不用指针的具体原因,归根结底就是:Go语言的类型是基于值传递的,要修改变量的值,就需要传递指针。
2. 结构体
结构体的定义很简单,基本和C一样:
type Person struct {
name string
age int
}
// 新增一个方法
func (p *Person) setAge(age int) {
p.age = age
}
func (p Person) getAge() {
return p.age
}
当然,结构体也是go的一种类型,也是可以添加方法的,按我的理解,其实结构体就相当于是面向对象的类,添加的方法就是成员方法,而本身的成员变量就是类中的成员变量。
结构体初始化:
结构体初始化有以下几种实现:
p := new(Person)p := &Person{}p := &Person{"zhangsan", 18}p := &Person{name: "zhangsan", age: 20}
NewXXX
func NewPerson(name string, age int) *Person {
return &Person{name, age}
}
3. 封装
回到面向对象三要素,封装,其实结构体就已经是封装的实现了。
这里有个注意的点就是:
类名,属性名,方法名,首字母大写表示对外(也就是其他包)可以访问,否则只能在本包内访问
4. 继承
go语言其实也是提供了继承的,只是采用的是组合的写法,比如以下例子:
// 定义父类
type Animal struct {
name string
age int
}
// 父类方法
func (a *Animal) Say() {
fmt.Println("animal say...")
}
// 定义子类继承父类
type Dog struct {
Animal
weight int
}
func main() {
d := &Dog{}
d.Say()
d.name = "旺财"
fmt.Println(d)
}
输出:
animal say...
&{{旺财 0} 0}
没有初始化值的变量会默认为对应类型的零值。
5. 多态
在理解go语言的多态之前,得先了解go语言的接口类型。
先来了解下其他语言的接口,在java中,对于接口的实现是必须在实现类中声明要实现的接口的,如果要实现一个接口,需要像下面代码这样编写代码:
// 定义一个接口类
public interface Person {
// 接口方法
public void say();
}
// 定义实现类,需要使用关键字implements显式的说明实现哪一个接口
class Teacher implements Person {
public void say() {
system.out.println("Hello 我是老师")
}
}
而在go语言中,一个类只要实现了接口要求的所有函数,就可以说这个类实现了这个接口,当然go中接口使用的关键字还是interface
比如:
有一个File类,并且该类有四个方法,Read(),Write(),Seek(),Close()
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
然后有以下一些接口:
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
代码中可以看出,File类并没有明确表示从这些接口中继承,甚至对于File类来说都不知道有这些接口的存在,但是在go里,认为File类实现了这些接口。
因此可以这样子进行赋值:
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)
实质上,这样子不就是多态么!
接口的赋值:
go语言中接口赋值分为以下两种情况:
type Writer interface {
Write(buf []byte) (n int, err error)
}
type ReadWriter interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
}
var file ReadWriter = new(File)
// 接口ReadWriter 赋值给 接口Writer
var file1 Writer = file
// 这样子是可以的,这样file1是Writer接口的实例,只有一个Write方法可以调用是正常的
var file Writer = new(File)
// 接口ReadWriter 赋值给 接口Writer
var file1 ReadWriter = file
// 这样子是不可以的,这样file1是ReadWriter接口的实例,当file1调用read方法时候,并没有这个方法,因为他实质是Writer接口类型
接口查询
接口查询可以检查接口所指向的对象实例是否实现了某个接口,从而进行接口转换,比如:
var file Writer = new(File)
if file1, ok := file.(ReadWriter); ok {
...
}
这里是Writer接口所指向的对象实例是File类,是实现了ReadWriter的,所以这里ok会为true,file1是ReadWriter接口的实例,所以相当于是从Writer接口转为了ReadWriter接口了。
万能类型
type any = interface{}interface{}
var a interface{} = new(int)
var b interface{} = new(string)
var c interface{} = struct{X int}{1}
a = 10
b = "hello"
fmt.Println(a, b, c) // 输出:10 hello {1}
interface{}interface{}
类型查询(类型断言)
interface{}xxx.(type)
func test(arg interface{}) {
switch arg.(type) {
case int:
fmt.Println("int type")
case string:
fmt.Println("string type")
default:
fmt.Println("unknown type")
}
}
func main() {
var v1 interface{} = "hello"
var v2 int = 100
v3 := struct{ X int }{1}
test(v1)
test(v2)
test(v3)
}
12. 学习资料
《Go语言编程》