记录零基础GO编程入门笔记之一

一、去安装和配置

下载并安装

  • 下载安装对应版本https://golang.org/dl/
  • 查看 go 安装目录/usr/local/go(Windows 下默认安装到c:\Go)
  • 运行go version命令检查是否安装正确

注意:推荐大家使用默认安装方式

项目环境变量
因为是以importgo,故添加了一个IMPORTGOROOT的环境变量进行了所有的代码开发和演示,具体配置如下:

$ vi ~/.profile

export IMPORTGOROOT=$HOME/importgo
export GOPATH=$IMPORTGOROOT # 覆盖 GOPATH 环境变为 importgo
export PATH=$IMPORTGOROOT/bin:$PATH #

当我们配置完毕后,可以执行source ~/.profile更新系统环境变量。

1.1写我的第一个去程序

首先需要在 GOPATH 文件夹下创建一个src目录用于我们举办的源代码。

$ mkdir -p $GOPATH/src

然后我们在 src 目录下面新建hello/hello.go的文件,内容如下:

包主

导入 “fmt”

func  main () {
     fmt . Println ( "你好,世界" )
}

我们使用go run hello.go来运行程序,输出为:

hello, world

二、Go 开发利器:VSCode

2.1为什么选择VSCode?

  • 微软官方出品,更新更新快
  • 一个插件:代码跳转,自动格式化,错误检测等

2.2下载安装

  • 登录 vscode 官网:https ://code.visualstudio.com
  • 视情况选择对应包下载

2.3将code命令添加到系统PATH中

效果:在code <filename/foldername>终端输入用vscode打开文件或文件夹

  • 以 mac 为例:在 vscode 中使用动态更新command + shif + p,
  • 输入shell命令,选择Shell Command:Install ‘code’ command in PATH,

如下图:

在这里插入图片描述

2.4安装 Go 插件

  • 登录 vscode 官网Extensions模块:https://marketplace.visualstudio.com/VSCode

  • 搜索go插件

  • 安装推荐lukehoban的 go 插件:https://marketplace.visualstudio.com/items?itemName=lukehoban.Go

  • 点击install后就打开vscode界面,进行安装

在这里插入图片描述

2.5去开发相关配置

"files.autoSave": "onFocusChange",
"editor.formatOnSave": true,
"go.gopath":"${workspaceRoot}:/Users/jinxue/golib", // 当前工作空间${wordspaceRoot}加上系统 GOPATH 目录
"go.goroot": "/usr/local/Cellar/go/1.9/libexec", // go 的安装目录
"go.formatOnSave": true, //在保存代码时自动格式化代码
"go.formatTool": "goimports", //使用 goimports 工具进行代码格式化,或者使用 goreturns 和 gofmt
"go.buildOnSave": true, //在保存代码时自动编译代码
"go.lintOnSave": true, //在保存代码时自动检查代码可以优化的地方,并给出建议
"go.vetOnSave": false, //在保存代码时自动检查潜在的错误
"go.coverOnSave": false, //在保存代码时执行测试,并显示测试覆盖率
"go.useCodeSnippetsOnFunctionSuggest": true, //使用代码片段作为提示
"go.gocodeAutoBuild": false //代码自动编译构建

三、基础知识

3.1命名规范

去语言中,任何一些(变量,常量,函数,自定义类型等)都应该满足以下的习惯:

  • 的连续字符(Unicode字母| _)或数字( “0” … “9”)组成。
  • 以字符或下划线开头。
  • 不能和去关键字冲突。

去关键字:

中断        默认      func         接口    选择
case          defer         go            map           struct 
chan          else          goto          package       switch 
const         fallthrough   if            range         type 
continue      for           import        return        var

举例说明:

foo  #合法
foo1 #合法
_foo #合法
变量 #合法
变量1 #合法
_变量 合法

1foo #不合法
1 #不合法
type #不合法
go #不合法

3.2变量

类型声明基本语法
在 Go 语言中,采用的是后置类型的声明方式,形如:

<命名> <类型>

例如:

x int // 定义 x 为整数类型

这么定义不是为了弥缝,而是为了让声明变得更加清晰易懂,具体可以参考文章gos-declaration-syntax

变量声明
在 Go 语言中通常我们使用关键字var来声明变量,例如

变种 X  INT  //表示声明一个名为X的整数变量
变种 b  INT  =  1  //表示声明一个名为b的整数变量,并且附上初始值为1个
变种 b  =  1

如果有多个变量同时声明,我们可以采用增加局部声明var的方式:

var (
     a , b  int   // 同时声明 a, b 的突然
    c  float64 
)

简短声明方式
变量在声明的时候,如果有最终值,我们可以使用:=的简短声明方式:

a  :=  1  // 声明 a 为 1 的紧急情况
b  :=  int64 ( 1 )   // 声明 b 为 1 的 64 位紧急

3.3恒定定义

定义是指值不能改变的,它必须满足以下规则:

  • 定义的时候,必须指定值
  • 指定的值类型主要有三类,布尔值,数字,字符串,各种数字类型包含(符文,整数,浮点数,复数),属于基本数据类型。
  • 不能使用 :=

例子:

const  a  =  64  // 定义常量值为 64

常量(
   b  =  4 
  c  =  0.1 
)

四、基础数据类型

Go语言中的基本数据类型包括:

bool	  the set of boolean (true, false)

uint8	  the set of all unsigned  8-bit integers (0 to 255)
uint16	  the set of all unsigned 16-bit integers (0 to 65535)
uint32	  the set of all unsigned 32-bit integers (0 to 4294967295)
uint64	  the set of all unsigned 64-bit integers (0 to 18446744073709551615)

int8	  the set of all signed  8-bit integers (-128 to 127)
int16	  the set of all signed 16-bit integers (-32768 to 32767)
int32	  the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64	  the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)

float32	  the set of all IEEE-754 32-bit floating-point numbers
float64	  the set of all IEEE-754 64-bit floating-point numbers

complex64	  the set of all complex numbers with float32 real and imaginary parts
complex128	  the set of all complex numbers with float64 real and imaginary parts

byte	  alias for uint8
rune	  alias for int32
uint	  either 32 or 64 bits
int	  same size as uint
uintptr	  an unsigned integer large enough to store the uninterpreted bits of a pointer value

string	  the set of string value (eg: "hi")

我们可以将基本类型分为三大类:

  1. 布尔类型
  2. 数字类型
  3. 字符串类型

1.布尔类型
一般我们用于判断条件,它的取值范围为true,声明false如下:

var  a  bool 
var  a  =  true 
a  :=  true

const  a  =

2.数字类型:
数字类型主要分为有符号数和无符号数,有符号数可以表示负数,除此之外它们还有其他的区别,不同的单独代表它们的实际存储空间,以及取它们的实际存储值的范围。

例如:

var (
   a  uint8  =  1 
  b  int8  =  1
)

var (
     c  int  =  64 
)

注意:

  1. 过去数字类型资产取值范围,超过了取值范围,出现overflows的错误。
  2. int,u的长度由他们自己的int决定,在32位系统里面,的长度未32位,64位系统,长度为64位。

3.字符串

var  a  =  "hello"  //单行字符串
var  c  =  " \" "  // 转义符

var  b  =  `hello`  //原样输出
var  d  =  ` 
line3 //多行输出line1 
line2 
`

var  str  =  "你好,世界"

b  :=  str [ 0 ]   //b 是 uint8 类型,类似于 byte 
fmt。Println ( b )   // 104 
fmt . Println ( string ( b )) // h

.mt . Println ( len ( str )) //12、查看字符串有多少个字节
fmt . Println ( len ([] rune ( str ))) // 8 查看有多少个字符

4.特殊类型

  1. byte,uint8 别名,用于表示二进制数据的字节
  2. rune,int32 别名,用于表示一个符号
var  str  =  "你好,世界"

对于 _ , char  :=  range  str {
   fmt . printf ( "%T" , char )
}

五、高级数据类型

5.1数组

定义:

  1. 由多个相同类型的元素组成的序列
  2. 一组的长度是固定的,声明后无法改变
  3. 数组的长度是数组类型的,例如:元素类型相同但长度不同的两个数组是不同类型的
  4. 需要严格控制程序所使用的内存时间,各有千秋,因为其固定的长度,有内存二次分配操作

示例

包主

导入 “fmt”

func  main () {
	 // 定义长度为 5 的数组
	var  arr1 [ 5 ] int 
	for  i  :=  0 ;<  5 ; i ++ {
		 arr1 [ i ] =  i
	}
	打印助手(“arr1”,arr1)

	//以下赋值会报告类型不匹配错误,因为数组长度是数组类型的部分
	// arr1 = [3]int{1, 2, 3} 
	arr1  = [ 5 ] int { 2 , 3 , 4 , 5 , 6 } // 长度和元素类型相同,可以正确赋值

	// 简写在定义的同时给出模式,
	arr2  := [ 5 ] int { 0 , 1 , 2 , 3 , 4 }
	 printHelper ( "arr2" , arr2 )

	//数组元素类型相同并且数组长度相等的情况下,数组可以进行比较
	FMT。Println ( arr1  ==  arr2 )

	// 也可以不显式定义一大串长度,由编译器完成长度计算
	var  arr3  = [ ... ] int { 0 , 1 , 2 , 3 , 4 }
	 printHelper ( " arr3 " , arr3 )

	// 定义前四个元素为默认值 0,
	最终一个元素为 -1 var  arr4  = [ ... ] int { 4 : - 1 }
	 printHelper ( "arr4" , arr4 )

	// 多维数组
	var  twoD [ 2 ][ 3 ] int 
	for  i  :=  0 ;<  2 ; i ++ {
		对于 j  :=  0 ; j  <  3 ; j ++ {
			 twoD [ i ][ j ] =  i  +  j
		}
	}
	.mt . Println ( "twoD:" , twoD )
}

func  printHelper ( name  string , arr [ 5 ] int ) {
	 for  i  :=  0 ;<  5 ;++ {
		 fmt。printf ( "%v[%v]: %v \n " , name , i , arr [ i ])
	}

	// len 获取长度
	fmt . printf ( "%v 的 len: %v \n " , name , len ( arr ))

	// cap 也可以获取数组长度
	fmt . printf ( "%v 的上限: %v \n " , name , cap ( arr ))

	.mt . Println ()
}

5.2切片

切片组成要素:

  1. 指针:指向底层数组
  2. 长度:切片中元素的长度,不能大于容量
  3. 容量:指针所指向的底层数组的总容量

常见初始化方式

  1. 使用 make 初始化
slice := make([]int, 5)     // 初始化长度和容量都为 5 的切片
slice := make([]int, 5, 10) // 初始化长度为 5, 容量为 10 的切片
  1. 使用简短定义
slice := []int{1, 2, 3, 4, 5}
  1. 使用数组来初始化切片
arr := [5]int{1, 2, 3, 4, 5}
slice := arr[0:3] // 左闭右开区间,最终切片为 [1,2,3]
  1. 使用切片来初始化切片
sliceA := []int{1, 2, 3, 4, 5}
sliceB := sliceA[0:3] // 左闭右开区间,sliceB 最终为 [1,2,3]

长度和容量

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	fmt.Println("len: ", len(slice))
	fmt.Println("cap: ", cap(slice))

	//改变切片长度
	slice = append(slice, 6)
	fmt.Println("after append operation: ")
	fmt.Println("len: ", len(slice))
	fmt.Println("cap: ", cap(slice)) //注意,底层数组容量不够时,会重新分配数组空间,通常为两倍
}

以上代码,预期输出如下:

len:  5
cap:  5
after append operation:
len:  6
cap:  12

注意点

  1. 多个切片共享一个底层数组的情况

对底层数组的修改,将影响上层多个切片的值

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	newSlice := slice[0:3]
	fmt.Println("before modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
	fmt.Println()

	newSlice[0] = 6
	fmt.Println("after modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
}

以上代码预期输出如下:

before modify underlying array:
slice:  [1 2 3 4 5]
newSlice:  [1 2 3]

after modify underlying array:
slice:  [6 2 3 4 5]
newSlice:  [6 2 3]
  1. 使用 copy 方法可以避免共享同一个底层数组

示例代码如下:

package main

import (
	"fmt"
)

func main() {
	slice := []int{1, 2, 3, 4, 5}
	newSlice := make([]int, len(slice))
	copy(newSlice, slice)
	fmt.Println("before modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
	fmt.Println()

	newSlice[0] = 6
	fmt.Println("after modifying underlying array:")
	fmt.Println("slice: ", slice)
	fmt.Println("newSlice: ", newSlice)
}

以上代码预期输出如下:

before modifying underlying array:
slice:  [1 2 3 4 5]
newSlice:  [1 2 3 4 5]

after modifying underlying array:
slice:  [1 2 3 4 5]
newSlice:  [6 2 3 4 5]

小练习
如何使用 copy 函数进行切片部分拷贝?

// 假设切片 slice 如下:
slice := []int{1, 2, 3, 4, 5}

// 如何使用 copy 创建切片 newSlice, 该切片值为 [2, 3, 4]
newSlice = copy(?,?)

5.3Map

在 Go 语言里面,map 一种无序的键值对, 它是数据结构 hash 表的一种实现方式,类似 Python 中的字典。

语法
使用关键字 map 来声明形如:

map[KeyType]ValueType

注意点:

  • 必须指定 key, value 的类型,插入的纪录类型必须匹配。
  • key 具有唯一性,插入纪录的 key 不能重复。
  • KeyType 可以为基础数据类型(例如 bool, 数字类型,字符串), 不能为数组,切片,map,它的取值必须是能够使用 ==进行比较。
  • ValueType 可以为任意类型。
  • 无序性。
  • 线程不安全, 一个 goroutine 在对 map 进行写的时候,另外的 goroutine 不能进行读和写操作,Go 1.6版本以后会抛出 runtime 错误信息。

声明和初始化

  1. 使用 var 声明
var cMap map[string]int  // 只定义, 此时 cMap 为 nil
fmt.Println(cMap == nil)
cMap["北京"] = 1  // 报错,因为 cMap 为 nil
  1. 使用 make
cMap := make(map[string]int)
cMap["北京"] = 1

// 指定初始容量
cMap = make(map[string]int, 100)
cMap["北京"] = 1

说明:在使用 make 初始化 map 的时候,可以指定初始容量,这在能预估 map key 数量的情况下,减少动态分配的次数,从而提升性能。

  1. 简短声明方式
cMap := map[string]int{"北京": 1}

map 基本操作

cMap := map[string]int{}

cMap["北京"] = 1 //写

code := cMap["北京"] // 读
fmt.Println(code)

code = cMap["广州"]  // 读不存在 key
fmt.Println(code)

code, ok = cMap["广州"]  // 检查 key 是否存在
if ok {
  fmt.Println(code)  
} else {
  fmt.Println("key not exist")  
}

delete(cMap, "北京") // 删除 key
fmt.Println("北京")

循环和无序性

cMap := map[string]int{"北京": 1, "上海": 2, "广州": 3, "深圳": 4}

for city, code := range cMap {
  fmt.Printf("%s:%d", city, code)
  fmt.Println()
}

线程不安全

cMap := make(map[string]int)

var wg sync.WaitGroup
wg.Add(2)

go func() {
	cMap["北京"] = 1
	wg.Done()
}()

go func() {
	cMap["上海"] = 2
	wg.Done()
}()

wg.Wait()

在 Go 1.6 之后的版本,多次运行此段代码,你将遇到这样的错误信息:

fatal error: concurrent map writes

goroutine x [running]:
runtime.throw(0x10c64b6, 0x15)
.....

解决之道:

  • 对读写操作加锁
  • 使用 security map, 例如 sync.map

map 嵌套

provinces := make(map[string]map[string]int)

provinces["北京"] = map[string]int{
  "东城区": 1,
  "西城区": 2,
  "朝阳区": 3,
  "海淀区": 4,
}

fmt.Println(provinces["北京"])

5.4自定义类型

前面我们已经学习了不少基础和高级数据类型,在 Go 语言里面,我们还可以通过自定义类型来表示一些特殊的数据结构和业务逻辑。

使用关键字 type 来声明:

 type NAME TYPE

声明语法

  1. 单次声明
type City string
  1. 批量声明
type (
    B0 = int8
    B1 = int16
    B2 = int32
    B3 = int64
)

type (
    A0 int8
    A1 int16
    A2 int32
    A3 int64
)

简单示例

package main

import "fmt"

type City string

func main() {
    city := City("上海")
    fmt.Println(city)
}

基本操作

package main

import "fmt"

type City string
type Age int

func main() {
    city := City("北京")
    fmt.Println("I live in", city + " 上海")  //  字符串拼接
    fmt.Println(len(city))  // len 方法

    middle := Age(12)

    if middle >= 12 {
        fmt.Println("Middle is bigger than 12")
    }
}

总结: 自定义类型的原始类型的所有操作同样适用。

函数参数

package main

import "fmt"

type Age int

func main() {
    middle := Age(12)
    printAge(middle)
}

func printAge(age int) {
    fmt.Println("Age is", age)
}

当我们运行代码的时候会出现 ./main.go:11:10: cannot use middle (type Age) as type int in argument to printAge 的错误。

因为 printAge 方法期望的是 int 类型,但是我们传入的参数是 Age,他们虽然具有相同的值,但为不同的类型。

我们可以采用显式的类型转换( printAge(int(primary)))来修复。

不同自定义类型间的操作

package main

import "fmt"

type Age int
type Height int

func main() {
    age := Age(12)
    height := Height(175)

    fmt.Println(height / age)
}

当我们运行代码会出现 ./main.go:12:21: invalid operation: height / age (mismatched types Height and Age) 错误,修复方法使用显式转换:

fmt.Println(int(height) / int(age))

5.5结构体

数组、切片和 Map 可以用来表示同一种数据类型的集合,但是当我们要表示不同数据类型的集合时就需要用到结构体。

结构体是由零个或多个任意类型的值聚合成的实体

关键字 typestruct 用来定义结构体:

type StructName struct{
    FieldName type
}

简单示例:定义一个学生结构体

package main

import "fmt"

type Student struct {
	Age     int
	Name    string
}

func main() {
	stu := Student{
		Age:     18,
		Name:    "name",
	}
	fmt.Println(stu)

	// 在赋值的时候,字段名可以忽略
	fmt.Println(Student{20, "new name"})

	return
}

通常结构体中一个字段占一行,但是类型相同的字段,也可以放在同一行,例如:

type Student struct{
    Age           int
    Name, Address string
}

一个结构体中的字段名是唯一的,例如一下代码,出现了两个 Name 字段,是错误的:

type Student struct{
    Name string
    Name string
}

结构体中的字段如果是小写字母开头,那么其他 package 就无法直接使用该字段,例如:

// 在包 pk1 中定义 Student 结构体
package pk1
type Student struct{
    Age  int
    name string
}
// 在另外一个包 pk2 中调用 Student 结构体
package pk2

func main(){
    stu := Student{}
    stu.Age = 18        //正确
    stu.name = "name"  // 错误,因为`name` 字段为小写字母开头,不对外暴露
}

结构体中可以内嵌结构体
但是需要注意的是:如果嵌入的结构体是本身,那么只能用指针。请看以下例子。

package main

import "fmt"

type Tree struct {
	value       int
	left, right *Tree
}

func main() {
	tree := Tree{
		value: 1,
		left: &Tree{
			value: 1,
			left:  nil,
			right: nil,
		},
		right: &Tree{
			value: 2,
			left:  nil,
			right: nil,
		},
	}

	fmt.Printf(">>> %#v\n", tree)
}

结构体是可以比较的
前提是结构体中的字段类型是可以比较的

package main

import "fmt"

type Tree struct {
	value       int
	left, right *Tree
}

func main() {
	tree1 := Tree{
		value: 2,
	}

	tree2 := Tree{
		value: 1,
	}

	fmt.Printf(">>> %#v\n", tree1 == tree2)
}

结构体内嵌匿名成员
声明一个成员对应的数据类型而不指名成员的名字;这类成员就叫匿名成员

package main

import "fmt"

type Person struct {
	Age  int
    Name string
}

type Student struct {
	Person
}

func main() {
	per := Person{
		Age:  18,
		Name: "name",
	}

	stu := Student{Person: per}

	fmt.Println("stu.Age: ", stu.Age)
	fmt.Println("stu.Name: ", stu.Name)
}

5.6函数

函数是语句序列的集合,能够将一个大的工作分解为小的任务,对外隐藏了实现细节

函数组成:

  • 函数名
  • 参数列表(parameter-list)
  • 返回值(result-list)
  • 函数体(body)
func name(parameter-list) (result-list){
    body
}
  1. 单返回值函数
func plus(a, b int) (res int){
	return a + b
}
  1. 多返回值函数
func multi()(string, int){
    return "name", 18
}
  1. 命名返回值
// 被命名的返回参数的值为该类型的默认零值
// 该例子中 name 默认初始化为空字符串,height 默认初始化为 0
func namedReturnValue()(name string, height int){
    name = "xiaoming"
    height = 180
    return
}
  1. 参数可变函数
func sum(nums ...int)int{
    fmt.Println("len of nums is : ", len(nums))
    res := 0
    for _, v := range nums{
        res += v
    }
    return res
}

func main(){
    fmt.Println(sum(1))
    fmt.Println(sum(1,2))
    fmt.Println(sum(1,2,3))
}
  1. 匿名函数
func main(){
    func(name string){
       fmt.Println(name)
    }("禾木课堂")
}
  1. 闭包
func main() {
	addOne := addInt(1)
	fmt.Println(addOne())
	fmt.Println(addOne())
	fmt.Println(addOne())

	addTwo := addInt(2)
	fmt.Println(addTwo())
	fmt.Println(addTwo())
	fmt.Println(addTwo())
}

func addInt(n int) func() int {
	i := 0
	return func() int {
		i += n
		return i
	}
}
  1. 函数作为参数
func sayHello(name string) {
	fmt.Println("Hello ", name)
}

func logger(f func(string), name string) {
	fmt.Println("start calling method sayHello")
	f(name)
	fmt.Println("end calling method sayHellog")
}

func main() {
	logger(sayHello, "禾木课堂")
}
  1. 传值和传引用
func sendValue(name string) {
	name = "hemuketang"
}

func sendAddress(name *string) {
	*name = "hemuketang"
}

func main() {
	// 传值和传引用
	str := "禾木课堂"
	fmt.Println("before calling sendValue, str : ", str)
	sendValue(str)
	fmt.Println("after calling sendValue, str : ", str)

	fmt.Println("before calling sendAddress, str : ", str)
	sendAddress(&str)
	fmt.Println("after calling sendAddress, str: ", str)
}

5.7方法

方法主要源于 OOP 语言,在传统面向对象语言中 (例如 C++), 我们会用一个“类”来封装属于自己的数据和函数,这些类的函数就叫做方法。

虽然 Go 不是经典意义上的面向对象语言,但是我们可以在一些接收者(自定义类型,结构体)上定义函数,同理这些接收者的函数在 Go 里面也叫做方法。

声明

方法(method)的声明和函数很相似, 只不过它必须指定接收者:

func (t T) F() {}

注意:

  • 接收者的类型只能为用关键字 type 定义的类型,例如自定义类型,结构体。
  • 同一个接收者的方法名不能重复 (没有重载),如果是结构体,方法名还不能和字段名重复。
  • 值作为接收者无法修改其值,如果有更改需求,需要使用指针类型。

简单例子

package main

type T struct{}

func (t T) F()  {}

func main() {
	t := T{}
	t.F()
}

接收者类型不是任意类型
例如:

package main

func (t int64) F()  {}

func main() {
	t := int64(10)
	t.F()
}

当运行以下代码会得到 cannot define new methods on non-local type int64 类似错误信息,我们可以使用自定义类型来解决:

package main

type T int64
func (t T) F()  {}

func main() {
	t := T(10)
	t.F()
}

小结:接收者不是任意类型,它只能为用关键字 type 定义的类型(例如自定义类型,结构体)。

命名冲突
a. 接收者定义的方法名不能重复, 例如:

package main

type T struct{}

func (T) F()         {}
func (T) F(a string) {}

func main() {
	t := T{}
	t.F()
}

运行代码我们会得到 method redeclared: T.F 类似错误。

b. 结构体方法名不能和字段重复,例如:

package main

type T struct{
  F string
}

func (T) F(){}

func main() {
	t := T{}
	t.F()
}

运行代码我们会得到 : type T has both field and method named F 类似错误。

小结: 同一个接收者的方法名不能重复 (没有重载);如果是结构体,方法名不能和字段重复。

接收者可以同时为值和指针
在 Go 语言中,方法的接收者可以同时为值或者指针,例如:

package main

type T struct{}

func (T) F()  {}
func (*T) N() {}

func main() {
	t := T{}
	t.F()
	t.N()

	t1 := &T{} // 指针类型
	t1.F()
	t1.N()
}

可以看到无论值类型 T 还是指针类型 &T 都可以同时访问 FN 方法。

值和指针作为接收者的区别
同样我们先看一段代码:

package main

import "fmt"

type T struct {
	value int
}

func (m T) StayTheSame() {
	m.value = 3
}

func (m *T) Update() {
	m.value = 3
}

func main() {
	m := T{0}
	fmt.Println(m) // {0}

	m.StayTheSame()
	fmt.Println(m) // {0}

	m.Update()
	fmt.Println(m) // {3}
}

运行代码输出结果为:

{0}
{0}
{3}

小结:值作为接收者(T) 不会修改结构体值,而指针 *T 可以修改。

5.8接口

接口类型是一种抽象类型,是方法的集合,其他类型实现了这些方法就是实现了这个接口。

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

简单示例:打印不同几何图形的面积和周长

package main

import (
	"fmt"
	"math"
)

type geometry interface {
	area() float32
	perim() float32
}

type rect struct {
	len, wid float32
}

func (r rect) area() float32 {
	return r.len * r.wid
}

func (r rect) perim() float32 {
	return 2 * (r.len + r.wid)
}

type circle struct {
	radius float32
}

func (c circle) area() float32 {
	return math.Pi * c.radius * c.radius
}

func (c circle) perim() float32 {
	return 2 * math.Pi * c.radius
}

func show(name string, param interface{}) {
	switch param.(type) {
	case geometry:
		// 类型断言
		fmt.Printf("area of %v is %v \n", name, param.(geometry).area())
		fmt.Printf("perim of %v is %v \n", name, param.(geometry).perim())
	default:
		fmt.Println("wrong type!")
	}
}

func main() {
	rec := rect{
		len: 1,
		wid: 2,
	}
	show("rect", rec)

	cir := circle{
		radius: 1,
	}
	show("circle", cir)

	show("test", "test param")
}

接口中可以内嵌接口
对上述例子做以下修改:

  • 首先添加 tmp 接口,该接口定义了 area() 方法
  • tmp 作为 geometry 接口中的匿名成员,并且将 geometry 接口中原本定义的 area() 方法删除

完成以上两步后,geometry 接口将会拥有 tmp 接口所定义的所有方法。运行结果和上述例子相同。

type tmp interface{
	area() float32
}

type geometry interface {
	// area() float32
	tmp
	perim() float32
}

看到这一定是真爱,更多编程精彩文章可以关注我看我主页。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值