package main
import (
"log"
"time"
"github.com/robfig/cron"
)
func main() {
i := 0
c := cron.New()
c.AddFunc("*/5 * * * * *", func() {
log.Println("i = ", i)
})
c.Start()
for {
time.Sleep(time.Second)
}
}
*/5 * * * * *
2018/01/10 16:13:25 i = 0
2018/01/10 16:13:30 i = 0
2018/01/10 16:13:35 i = 0
2018/01/10 16:13:40 i = 0
...
5 * * * * *
2018/01/10 16:23:05 i = 0
2018/01/10 16:24:05 i = 0
2018/01/10 16:25:05 i = 0
...
@every 2s
2018/01/10 16:29:22 i = 0
2018/01/10 16:29:24 i = 0
2018/01/10 16:29:26 i = 0
...
@every 1m10s
2018/01/10 16:31:22 i = 0
2018/01/10 16:32:32 i = 0
2018/01/10 16:33:42 i = 0
...
@every 1h30m10s
Package cron implements a cron spec parser and job runner.
Usage
Callers may register Funcs to be invoked on a given schedule. Cron will run them in their own goroutines.
c := cron.New()
c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })
c.AddFunc("@hourly", func() { fmt.Println("Every hour") })
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty") })
c.Start()
..
// Funcs are invoked in their own goroutine, asynchronously.
...
// Funcs may also be added to a running Cron
c.AddFunc("@daily", func() { fmt.Println("Every day") })
..
// Inspect the cron job entries' next and previous run times.
inspect(c.Entries())
..
c.Stop() // Stop the scheduler (does not stop any jobs already running).
CRON Expression Format
A cron expression represents a set of times, using 6 space-separated fields.
Field name | Mandatory? | Allowed values | Allowed special characters
---------- | ---------- | -------------- | --------------------------
Seconds | Yes | 0-59 | * / , -
Minutes | Yes | 0-59 | * / , -
Hours | Yes | 0-23 | * / , -
Day of month | Yes | 1-31 | * / , - ?
Month | Yes | 1-12 or JAN-DEC | * / , -
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
Note: Month and Day-of-week field values are case insensitive. “SUN”, “Sun”, and “sun” are equally accepted.
Special Characters
Asterisk ( * )
The asterisk indicates that the cron expression will match for all values of the field; e.g., using an asterisk in the 5th field (month) would indicate every month.
Slash ( / )
Slashes are used to describe increments of ranges. For example 3-59/15 in the 1st field (minutes) would indicate the 3rd minute of the hour and every 15 minutes thereafter. The form “*\/…” is equivalent to the form “first-last/…”, that is, an increment over the largest possible range of the field. The form “N/…” is accepted as meaning “N-MAX/…”, that is, starting at N, use the increment until the end of that specific range. It does not wrap around.
Comma ( , )
Commas are used to separate items of a list. For example, using “MON,WED,FRI” in the 5th field (day of week) would mean Mondays, Wednesdays and Fridays.
Hyphen ( - )
Hyphens are used to define ranges. For example, 9-17 would indicate every hour between 9am and 5pm inclusive.
Question mark ( ? )
Question mark may be used instead of ‘*’ for leaving either day-of-month or day-of-week blank.
Predefined schedules
You may use one of several pre-defined schedules in place of a cron expression.
Entry | Description | Equivalent To
----- | ----------- | -------------
@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 *
@monthly | Run once a month, midnight, first of month | 0 0 0 1 * *
@weekly | Run once a week, midnight on Sunday | 0 0 0 * * 0
@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * *
@hourly | Run once an hour, beginning of hour | 0 0 * * * *
Intervals
You may also schedule a job to execute at fixed intervals, starting at the time it’s added or cron is run. This is supported by formatting the cron spec like this:
@every <duration>
where “duration” is a string accepted by time.ParseDuration http://golang.org/pkg/time/#ParseDuration.
For example, “@every 1h30m10s” would indicate a schedule that activates immediately, and then every 1 hour, 30 minutes, 10 seconds.
Note: The interval does not take the job runtime into account. For example, if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, it will have only 2 minutes of idle time between each run.
Time zones
All interpretation and scheduling is done in the machine’s local time zone (as provided by the Go time package http://www.golang.org/pkg/time.
Be aware that jobs scheduled during daylight-savings leap-ahead transitions will not be run!
Thread safety
Since the Cron service runs concurrently with the calling code, some amount of care must be taken to ensure proper synchronization.
All cron methods are designed to be correctly synchronized as long as the caller ensures that invocations have a clear happens-before ordering between them.
Implementation
Cron entries are stored in an array, sorted by their next activation time. Cron sleeps until the next job is due to be run.
Upon waking:
简单易懂的 Go 函数带图教程
注意:该教程仅介绍 Go 函数,不包括:可变参数、延迟函数、外部函数、方法、HTTP、封包编码等。
函数是一个独立的,可以被重用的,可以一次又一次运行的代码块。函数可以有输入参数,也可以有返回值输出。
声明了一个函数 “Len”,输入参数为 “s”,类型为 “string”,返回值类型为 “int”。
func Len(s string) int {
return utf8.RuneCountInString(s)
}
Len("Hello world 👋")
输入参数被用来把数据传递给函数。返回值类型被用来从函数中返回数据。从函数中返回的数据被称为“返回值”。
采用一个名为 “s” 的 string 类型“输入参数”,并返回一个“返回值类型”为 int 的没有名字的返回值。
函数签名就是一个函数的类型 – 由输入参数类型和返回值类型组成。
func jump()
// 签名:func()
func Len(s string) int
// 签名:func(string) int
func multiply(n ...float64) []float64
// 签名:func(...float64) []float64
Go 语言中的函数是一等公民,可以被任意赋值传递。
flen := Len
flen("Hello!")
一个函数签名的示例代码。
当一个函数被调用时,它的主体将以提供的输入参数运行。如果函数声明了至少一个返回值类型,那么函数将会返回一个或多个返回值。
你可以直接从 RuneCountInString 函数返回,因为它也返回一个 int。
func Len(s string) int {
return utf8.RuneCountInString(s)
}
lettersLen := Len("Hey!")
这个函数使用表达式作为返回值。
func returnWithExpression(a, b int) int {
return a * b * 2 * anotherFunc(a, b)
}
每一组括号都会创建一个新的函数块,任何在函数块内声明的标识符只在该函数块内可见。
const message = "Hello world 👋"
func HelloWorld() {
name := "Dennis"
message := "Hello, earthling!"
}
HelloWorld()
/*
★ message 常量在这里可见。
★ 在函数内的变量 name 在这里不可见。
★ 在函数内被隐藏的变量 message 在这里不可见。
*/
现在,让我们看看输入参数和返回值类型不同风格的声明方式。
声明一个类型为 “String” 的输入参数 “s”,和一个整数返回值类型。
一个函数的输入参数和返回值类型就像变量一样起作用。
Niladic 函数不接受任何输入参数。
func tick() {
fmt.Println( time.Now().Format( time.Kitchen ) )
}
tick()
// Output: 13:50pm etc.
如果一个函数没有返回值,你可以省略返回值类型和 return 这个关键字。
func square(n int) int {
return n * n
}
square(4)
// Output: 16
当函数只返回一个返回值时,不要使用括号。
func scale(width, height, scale int) (int, int) {
return width * scale, height * scale
}
w, h := scale(5, 10, 2)
// Output: w is 10, h is 20
多个返回值类型应该用圆括号括起来。
Go 语言会自动为前面的参数声明类型。
这些声明是一样的:
func scale(width, height, scale int) (int, int)
func scale(width int, height int, scale int) (int, int)
一些函数通常会返回错误 – 多个返回值让这使用很方便。
func write(w io.Writer, str string) (int, error) {
return w.Write([]byte(s))
}
write(os.Stdout, "hello")
// Output: hello
从 Write 函数直接返回和返回多个返回值类型是相同的。因为它也返回一个 int 和一个错误值。
func write(w io.Writer, str string) (int, error) {
n, err := w.Write([]byte(s))
return n, err
}
如果一切正常,你可以直接返回 nil 作为结果:
func div(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("divide by zero")
}
return a / b, nil
}
r, err := div(-1, 0)
// err: divide by zero
你可以使用下划线来丢弃返回值。
/*
假设我们有如下函数:
*/
func TempDir(dir, prefix string) (name string, err error)
丢弃错误返回值(第 2 个返回值):
name, _ := TempDir("", "test")
丢弃全部返回值:
TempDir("", "test")
你也可以在未使用的输入参数中,把下划线当作名字使用。– 以满足一个接口为例(或者看这里)。
func Write(_ []byte) (n int, err error) {
return 0, nil
}
命名的返回值参数让你可以像使用变量一样使用返回值,而且它让你可以使用一个空的 return。
返回值 pos 的行为就像是一个变量,函数 biggest 通过一个空的 return 返回它(return 后面没有任何表达式)。
// biggest 返回切片 nums 中最大的数字的下标。
func biggest(nums []int) (pos int) {
if len(nums) == 0 {
return -1
}
m := nums[0]
for i, n := range nums {
if n > m {
m = n
pos = i
}
}
// returns the pos
return
}
pos := biggest([]int{4,5,1})
// Output: 1
上面的程序没有经过优化,时间复杂度为 O(n)。
当你使用命名返回值参数时,也有一个有争议的优化技巧,但编译器很快就会修复这个问题来禁止它的使用。
func incr(snum string) (rnum string, err error) {
var i int
// start of a new scope
if i, err := strconv.Atoi(snum); err == nil {
i = i + 1
}
// end of the new scope
rnum = strconv.Itoa(i)
return
}
incr("abc")
// Output: 0 and nil
变量 i 和 err 只在 if 代码块内可见。最后,错误不应该是 “nil”,因为 “abc” 不能被转化为整数,所以这是一个错误,但是我们没有发现这个错误。
点击这里查看该问题解决方案。
函数 pass 把输入参数的值设置为了对应的零值。
func pass(s string, n int) {
s, n = "", 0
}
我们传递两个变量给 pass 函数:
str, num := "knuth", 2
pass(str, num)
函数执行完,我们的两个变量的值没有任何变化。
str is "knuth"
num is 2
这是因为,当我们传递参数给函数时,参数被自动的拷贝了一份新的变量。这被叫做值传递。
下面这个函数接受一个指向 string 变量的指针。它修改了指针 ps 指向的值。然后它尝试将指针的值设置为 nil。所以,指针将不会再指向传递进来的 string 变量的地址。
func pass(ps *string) {
*ps = "donald"
ps = nil
}
我们定义了一个新的变量 s,然后我们通过 & 运算符来获取它的内存地址,并将它的内存地址保存在一个新的指针变量 ps 中。
s := "knuth"
ps := &s
让我们把 ps 传递给 pass 函数。
pass(ps)
在函数运行结束之后,我们会看到变量 s 的值已经改变。但是,指针 ps 仍然指向变量 s 的有效地址。
// Output:
// s : "donald"
// ps: 0x1040c130
指针 ps 是按值传递给函数 pass 的,只有它指向的地址被拷贝到了函数 pass 中的一个新的指针变量(形参)。所以,在函数里面把指针变量设置为 nil 对传递给函数做参数的指针(实参)没有影响。
&s
和 ps
是不同的变量,但是他们都指向相同的变量 s
。
到目前为止,我们已经学完了函数的参数声明方式。现在,让我们一起来看看如何正确的命名函数、输入参数和返回值类型。
使用函数的好处有增加代码的可读性和可维护性等。你可能需要根据实际情况选择性的采取这些意见。
当选择尽可能简短的命名。要选择简短、自描述而且有意义的名字。
// Not this:
func CheckProtocolIsFileTransferProtocol(protocolData io.Reader) bool
// This:
func Detect(in io.Reader) Name {
return FTP
}
// Not this:
func CreateFromIncomingJSONBytes(incomingBytesSource []byte)
// This:
func NewFromJSON(src []byte)
// This:
func runServer()
func RunServer()
// Not this:
func run_server()
func RUN_SERVER()
func RunSERVER()
缩略词应该全部大写:
// Not this:
func ServeHttp()
// This:
func ServeHTTP()
// Not this:
func encrypt(i1, a3, b2 byte) byte
// This:
func encrypt(privKey, pubKey, salt byte) byte
// Not this:
func Write(writableStream io.Writer, bytesToBeWritten []byte)
// This:
func Write(w io.Writer, s []byte)
// 类型就非常清晰了,没有必要再取名字了
// Not this:
func mongo(h string) error
// This:
func connectMongo(host string) error
// 如果这个函数是在包 Mongo 内,只要这样就好了:
func connect(host string) error
// Not this:
func pop(new bool) item
// This:
func pop(isNew bool) item
// Not this:
func show(errorString string)
// This:
func show(err string)
在 Go 语言中没有 Getters 和 Setters。但是,你可以通过函数来模拟。
// Not this:
func GetName() string
// This:
func Name() string
// Not this:
func Name() string
// This:
func SetName(name string)
因为我将在即将发布的文章中说明下面问题的一些解决方法,所以你可以不需要去 duckduckgo 或者 Google 去搜索答案。
💓 希望你能把这片文章分享给你的朋友。谢谢!
via: https://blog.learngoprogramming.com/golang-funcs-params-named-result-values-types-pass-by-value-67f4374d9c0a
作者:Inanc Gumus 译者:MDGSF 校对:校对者ID
已发布:https://studygolang.com/articles/12338
package main
import "log"
import "fmt"
import "strings"
func main() {
var data []byte
for i := 'a'; i <= 'z'; i++ {
data = append(data, byte(i))
}
log.Println("data = ", data)
msg := fmt.Sprintf("%x", data)
log.Println("data = ", msg)
log.Println("data = ", strings.ToUpper(msg))
}
package main
import (
"log"
"sort"
)
type Stu struct {
id int
name string
}
var class []*Stu
type ByID []*Stu
func (s ByID) Len() int {
return len(s)
}
func (s ByID) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByID) Less(i, j int) bool {
return s[i].id < s[j].id
}
type ByName []*Stu
func (s ByName) Len() int {
return len(s)
}
func (s ByName) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByName) Less(i, j int) bool {
return s[i].name < s[j].name
}
func NewDemoClass() {
stu1 := &Stu{11, "huang"}
stu2 := &Stu{2, "jian"}
stu3 := &Stu{33, "ping"}
stu4 := &Stu{4, "ao"}
stu5 := &Stu{5, "lili"}
class = append(class, stu1)
class = append(class, stu2)
class = append(class, stu3)
class = append(class, stu4)
class = append(class, stu5)
}
func showClass() {
for _, v := range class {
log.Println(*v)
}
}
func main() {
NewDemoClass()
sort.Sort(ByID(class))
showClass()
log.Println()
sort.Sort(ByName(class))
showClass()
log.Println()
}
package main
import (
"log"
"sort"
)
type Stu struct {
id int
name string
}
var class []Stu
type ByID []Stu
func (s ByID) Len() int {
return len(s)
}
func (s ByID) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByID) Less(i, j int) bool {
return s[i].id < s[j].id
}
type ByName []Stu
func (s ByName) Len() int {
return len(s)
}
func (s ByName) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByName) Less(i, j int) bool {
return s[i].name < s[j].name
}
func NewDemoClass() {
stu1 := Stu{11, "huang"}
stu2 := Stu{2, "jian"}
stu3 := Stu{33, "ping"}
stu4 := Stu{4, "ao"}
stu5 := Stu{5, "lili"}
class = append(class, stu1)
class = append(class, stu2)
class = append(class, stu3)
class = append(class, stu4)
class = append(class, stu5)
}
func showClass() {
for _, v := range class {
log.Println(v)
}
}
func main() {
NewDemoClass()
sort.Sort(ByID(class))
showClass()
log.Println()
sort.Sort(ByName(class))
showClass()
log.Println()
}
package main
import (
"fmt"
"sort"
)
type Shop struct {
Price float32
Date string
}
type ByPriceData []Shop
func (s ByPriceData) Len() int {
return len(s)
}
func (s ByPriceData) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s ByPriceData) Less(i, j int) bool {
if s[i].Price > s[j].Price {
return true
} else if s[i].Price == s[j].Price {
return s[i].Date < s[j].Date
} else {
return false
}
}
func main() {
shops := []Shop{
{1, "222"}, {7, "222"}, {3, "222"},
{4, "222"}, {5, "2018/1/21"}, {5, "2018/1/20"},
{5, "2018/1/16"}, {5, "2018/1/19"},
}
sort.Sort(ByPriceData(shops))
fmt.Println(shops)
}
$ go run test.go
[{7 222} {5 2018/1/16} {5 2018/1/19} {5 2018/1/20} {5 2018/1/21} {4 222} {3 222} {1 222}]
欢迎来到 Golang 系列教程 的第 17 个教程。
方法其实就是一个函数,在 func
这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。
下面就是创建一个方法的语法。
func (t Type) methodName(parameter list) {
}
上面的代码片段创建了一个接收器类型为 Type
的方法 methodName
。
让我们来编写一个简单的小程序,它会在结构体类型上创建一个方法并调用它。
package main
import (
"fmt"
)
type Employee struct {
name string
salary int
currency string
}
/*
displaySalary() 方法将 Employee 做为接收器类型
*/
func (e Employee) displaySalary() {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee {
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
emp1.displaySalary() //调用 Employee 类型的 displaySalary() 方法
}
在上面程序的第 16 行,我们在 Employee
结构体类型上创建了一个 displaySalary
方法。displaySalary()方法在方法的内部访问了接收器 e Employee
。在第 17 行,我们使用接收器 e
,并打印 employee 的 name、currency 和 salary 这 3 个字段。
在第 26 行,我们调用了方法 emp1.displaySalary()
。
程序输出:Salary of Sam Adolf is $5000
。
上面的程序已经被重写为只使用函数,没有方法。
package main
import (
"fmt"
)
type Employee struct {
name string
salary int
currency string
}
/*
displaySalary()方法被转化为一个函数,把 Employee 当做参数传入。
*/
func displaySalary(e Employee) {
fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
emp1 := Employee{
name: "Sam Adolf",
salary: 5000,
currency: "$",
}
displaySalary(emp1)
}
在上面的程序中,displaySalary
方法被转化为一个函数,Employee
结构体被当做参数传递给它。这个程序也产生完全相同的输出:Salary of Sam Adolf is $5000
。
既然我们可以使用函数写出相同的程序,那么为什么我们需要方法?这有着几个原因,让我们一个个的看看。
Go 不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。
相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。假设我们有一个 Square
和 Circle
结构体。可以在 Square
和 Circle
上分别定义一个 Area
方法。见下面的程序。
package main
import (
"fmt"
"math"
)
type Rectangle struct {
length int
width int
}
type Circle struct {
radius float64
}
func (r Rectangle) Area() int {
return r.length * r.width
}
func (c Circle) Area() float64 {
return math.Pi * c.radius * c.radius
}
func main() {
r := Rectangle{
length: 10,
width: 5,
}
fmt.Printf("Area of rectangle %d\n", r.Area())
c := Circle{
radius: 12,
}
fmt.Printf("Area of circle %f", c.Area())
}
该程序输出:
Area of rectangle 50
Area of circle 452.389342
上面方法的属性被使用在接口中。我们将在接下来的教程中讨论这个问题。
到目前为止,我们只看到了使用值接收器的方法。还可以创建使用指针接收器的方法。值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的情况不是这样的。让我们用下面的程序来帮助理解这一点。
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
使用值接收器的方法。
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
使用指针接收器的方法。
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name)
fmt.Printf("\n\nEmployee age before change: %d", e.age)
(&e).changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}
在上面的程序中,changeName
方法有一个值接收器 (e Employee)
,而 changeAge
方法有一个指针接收器 (e *Employee)
。在 changeName
方法中对 Employee
结构体的字段 name
所做的改变对调用者是不可见的,因此程序在调用 e.changeName("Michael Andrew")
这个方法的前后打印出相同的名字。由于 changeAge
方法是使用指针 (e *Employee)
接收器的,所以在调用 (&e).changeAge(51)
方法对 age
字段做出的改变对调用者将是可见的。该程序输出如下:
Employee name before change: Mark Andrew
Employee name after change: Mark Andrew
Employee age before change: 50
Employee age after change: 51
在上面程序的第 36 行,我们使用 (&e).changeAge(51)
来调用 changeAge
方法。由于 changeAge
方法有一个指针接收器,所以我们使用 (&e)
来调用这个方法。其实没有这个必要,Go语言让我们可以直接使用 e.changeAge(51)
。e.changeAge(51)
会自动被Go语言解释为 (&e).changeAge(51)
。
下面的程序重写了,使用 e.changeAge(51)
来代替 (&e).changeAge(51)
,它输出相同的结果。
package main
import (
"fmt"
)
type Employee struct {
name string
age int
}
/*
使用值接收器的方法。
*/
func (e Employee) changeName(newName string) {
e.name = newName
}
/*
使用指针接收器的方法。
*/
func (e *Employee) changeAge(newAge int) {
e.age = newAge
}
func main() {
e := Employee{
name: "Mark Andrew",
age: 50,
}
fmt.Printf("Employee name before change: %s", e.name)
e.changeName("Michael Andrew")
fmt.Printf("\nEmployee name after change: %s", e.name)
fmt.Printf("\n\nEmployee age before change: %d", e.age)
e.changeAge(51)
fmt.Printf("\nEmployee age after change: %d", e.age)
}
一般来说,指针接收器可以使用在:对方法内部的接收器所做的改变应该对调用者可见时。
指针接收器也可以被使用在如下场景:当拷贝一个结构体的代价过于昂贵时。考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。
在其他的所有情况,值接收器都可以被使用。
属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。
package main
import (
"fmt"
)
type address struct {
city string
state string
}
func (a address) fullAddress() {
fmt.Printf("Full address: %s, %s", a.city, a.state)
}
type person struct {
firstName string
lastName string
address
}
func main() {
p := person{
firstName: "Elon",
lastName: "Musk",
address: address {
city: "Los Angeles",
state: "California",
},
}
p.fullAddress() //访问 address 结构体的 fullAddress 方法
}
在上面程序的第 32 行,我们通过使用 p.fullAddress()
来访问 address
结构体的 fullAddress()
方法。明确的调用 p.address.fullAddress()
是没有必要的。该程序输出:
Full address: Los Angeles, California
这个话题很多Go语言新手都弄不明白。我会尽量讲清楚。
当一个函数有一个值参数,它只能接受一个值参数。
当一个方法有一个值接收器,它可以接受值接收器和指针接收器。
让我们通过一个例子来理解这一点。
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
func area(r rectangle) {
fmt.Printf("Area Function result: %d\n", (r.length * r.width))
}
func (r rectangle) area() {
fmt.Printf("Area Method result: %d\n", (r.length * r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
area(r)
r.area()
p := &r
/*
compilation error, cannot use p (type *rectangle) as type rectangle
in argument to area
*/
//area(p)
p.area()//通过指针调用值接收器
}
第 12 行的函数 func area(r rectangle)
接受一个值参数,方法 func (r rectangle) area()
接受一个值接收器。
在第 25 行,我们通过值参数 area(r)
来调用 area 这个函数,这是合法的。同样,我们使用值接收器来调用 area 方法 r.area()
,这也是合法的。
在第 28 行,我们创建了一个指向 r
的指针 p
。如果我们试图把这个指针传递到只能接受一个值参数的函数 area,编译器将会报错。所以我把代码的第 33 行注释了。如果你把这行的代码注释去掉,编译器将会抛出错误 compilation error, cannot use p (type *rectangle) as type rectangle in argument to area.
。这将会按预期抛出错误。
现在到了棘手的部分了,在第35行的代码 p.area()
使用指针接收器 p
调用了只接受一个值接收器的方法 area
。这是完全有效的。原因是当 area
有一个值接收器时,为了方便Go语言把 p.area()
解释为 (*p).area()
。
该程序将会输出:
Area Function result: 50
Area Method result: 50
Area Method result: 50
和值参数相类似,函数使用指针参数只接受指针,而使用指针接收器的方法可以使用值接收器和指针接收器。
package main
import (
"fmt"
)
type rectangle struct {
length int
width int
}
func perimeter(r *rectangle) {
fmt.Println("perimeter function output:", 2*(r.length+r.width))
}
func (r *rectangle) perimeter() {
fmt.Println("perimeter method output:", 2*(r.length+r.width))
}
func main() {
r := rectangle{
length: 10,
width: 5,
}
p := &r //pointer to r
perimeter(p)
p.perimeter()
/*
cannot use r (type rectangle) as type *rectangle in argument to perimeter
*/
//perimeter(r)
r.perimeter()//使用值来调用指针接收器
}
在上面程序的第 12 行,定义了一个接受指针参数的函数 perimeter
。第 17 行定义了一个有一个指针接收器的方法。
在第 27 行,我们调用 perimeter 函数时传入了一个指针参数。在第 28 行,我们通过指针接收器调用了 perimeter 方法。所有一切看起来都这么完美。
在被注释掉的第 33 行,我们尝试通过传入值参数 r
调用函数 perimeter
。这是不被允许的,因为函数的指针参数不接受值参数。如果你把这行的代码注释去掉并把程序运行起来,编译器将会抛出错误 main.go:33: cannot use r (type rectangle) as type *rectangle in argument to perimeter.
。
在第 35 行,我们通过值接收器 r
来调用有指针接收器的方法 perimeter
。这是被允许的,为了方便Go语言把代码 r.perimeter()
解释为 (&r).perimeter()
。该程序输出:
perimeter function output: 30
perimeter method output: 30
perimeter method output: 30
到目前为止,我们只在结构体类型上定义方法。也可以在非结构体类型上定义方法,但是有一个问题。为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。到目前为止,我们定义的所有结构体和结构体上的方法都是在同一个 main
包中,因此它们是可以运行的。
package main
func (a int) add(b int) {
}
func main() {
}
在上面程序的第 3 行,我们尝试把一个 add
方法添加到内置的类型 int
。这是不允许的,因为 add
方法的定义和 int
类型的定义不在同一个包中。该程序会抛出编译错误 cannot define new methods on non-local type int
。
让该程序工作的方法是为内置类型 int 创建一个类型别名,然后创建一个以该类型别名为接收器的方法。
package main
import "fmt"
type myInt int
func (a myInt) add(b myInt) myInt {
return a + b
}
func main() {
num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)
fmt.Println("Sum is", sum)
}
在上面程序的第5行,我们为 int
创建了一个类型别名 myInt
。在第7行,我们定义了一个以 myInt
为接收器的的方法 add
。
该程序将会打印出 Sum is 15
。
我已经创建了一个程序,包含了我们迄今为止所讨论的所有概念,详见github。
这就是Go中的方法。祝你有美好的一天。
下一章 接口 - I
via: https://golangbot.com/methods/
作者:Nick Coghlan 译者:MDGSF 校对:rxcai
已发布:https://studygolang.com/articles/12264
2017年就这么过去了,新的一年又到了。写这个博客估计是我坚持的最长时间的事情了,这应该归功于github,因为github的这个博客使用起来真的可以说是非常的方便简单。我之前在这博客里面一直都是只记录一些计算机相关的知识点,其中还有不少更是把代码一贴上去就不管了,只要自己看得懂就好了,没有别的原因,就是太懒了。今年我想要做些改变,在这上面写一些文字,记录下自己的成长过程。如果是技术点的话,尽可能写的通俗易懂一些。
首先要做下2017年的总结。在过去的一年里,我从厦门来到了深圳,3月份末尾来的。来到深圳的第一个星期就来上班,现在想想当时真的有点太累了。1、2月份的时候,那个时候我还在厦门帮忙做最后一个项目,我负责了客户端的C语言代码,是跑在单片机上的,挺好玩的。其实厦门那家公司挺好的,就是工资太低了。来了深圳之后进了一个游戏公司,现在还在这个公司,做棋牌游戏的。3月份刚进去那会,服务器还是用C++做的,我被安排去开发一款兰州麻将,我只想说我不会打麻将-_-。那个时候公司还挺乱的,无论是代码还是管理流程,没少加班,有段时间经常通宵到早上。不过我们的技术BOSS对我们还是不错的。这个项目持续了好几个月的时间。
中间有段时间闲了下来,就开始学习Golang,Golang今年在国内也特别的火。 然后现在改用Golang做服务器了,简单很多,写起来也很舒服,3周的时间,两个麻将的服务器逻辑就全部OK了。
2018年已经到了。我在这里给自己定了几个目标:
坚持写博客,还要写好。
坚持听“得到APP”的老师讲课。(很不错)
学习金融理财相关的知识。
学习新的技术就不列了,因为这是做IT必须具备的技能。
在这3个目标中,其实后面两个和我的本职工作并没有什么关系。但是前段时间,华为开除40出头程序员,中兴的40出头程序员跳楼,这些事情给我敲响了一个警钟。我只是希望我到了那个年龄的时候,能有更多回旋的余地。
另外,为什么需要把目标写在这里?因为有人做过调查,发现把目标写清楚,如果达到目标的方法写清楚,成功的几率会比只是在自己心里面想想要大得多。而我想要在这新的一年里面改变自己。加油!