使用值类型的方法修改接收器?

huangapple go评论72阅读模式
英文:

Modifying receiver with a method on value?

问题

package matrix

import (
	"errors"
	"strconv"
	"strings"
)

// Matrix 矩阵接口
type Matrix interface {
	Rows() [][]int
	Cols() [][]int
	Set(r, c, val int) bool
}

// matrix 实现了 Matrix 接口
type matrix struct {
	data [][]int
	rows int
	cols int
}

// New 根据输入创建一个有效的矩阵
func New(input string) (Matrix, error) {
	var m matrix
	rows := strings.Split(input, "\n")
	for r, row := range rows {
		rowElements := strings.Fields(row)

		switch {
		case r == 0:
			m.rows, m.cols = len(rows), len(rowElements)
			matrix, err := allocateMemory(m.rows, m.cols)
			if err != nil {
				return invalidMatrix()
			}
			m.data = matrix
		case len(rowElements) != m.cols:
			return invalidMatrix()
		}

		for c, element := range rowElements {
			element, err := strconv.Atoi(element)
			if err != nil {
				return invalidMatrix()
			}
			m.data[r][c] = element
		}
	}
	return m, nil
}

// invalidMatrix 返回一个表示提供的矩阵无效的错误
func invalidMatrix() (Matrix, error) {
	return nil, errors.New("invalid matrix")
}

// allocateMemory 分配一个大小为 RxC 的 int 类型的二维切片
func allocateMemory(R, C int) ([][]int, error) {
	if R < 1 || C < 1 {
		return nil, errors.New("invalid matrix")
	}
	matrix := make([][]int, R)
	for r := range matrix {
		matrix[r] = make([]int, C)
	}
	return matrix, nil
}

// Set 在矩阵的 (r,c) 处设置给定的值,
// 如果 (r,c) 属于矩阵,则返回 true。
func (m matrix) Set(r, c, val int) bool {
	switch {
	case r < 0 || c < 0:
		return false
	case r >= m.rows || c >= m.cols:
		return false
	default:
		m.data[r][c] = val
		return true
	}
}

// order 定义了导出矩阵的顺序
// 两个有用的值是 columnMajor 和 rowMajor
type order int

const (
	columnMajor order = iota
	rowMajor
)

// Cols 返回矩阵的列。
func (m matrix) Cols() [][]int {
	return m.export(columnMajor)
}

// Rows 返回矩阵的行。
func (m matrix) Rows() [][]int {
	return m.export(rowMajor)
}

// export 根据所需的顺序(columnMajor 或 rowMajor)返回矩阵。
func (m matrix) export(o order) [][]int {
	var matrix [][]int
	var err error
	switch o {
	case columnMajor:
		matrix, err = allocateMemory(m.cols, m.rows)
		if err != nil {
			return nil
		}
		for r, row := range m.data {
			for c, element := range row {
				matrix[c][r] = element
			}
		}
	case rowMajor:
		matrix, err = allocateMemory(m.rows, m.cols)
		if err != nil {
			return nil
		}
		for r, row := range m.data {
			copy(matrix[r], row)
		}
	}
	return matrix
}

我很难理解为什么 Set() 方法能够修改结构体的数据。我原本的理解是,值类型上定义的方法不能这样做。我尝试将其与另一个无法修改接收器内容的问题进行比较,但在这种情况下它却起作用了。这段代码的测试文件可以在test file中找到。你有什么想法,我可能漏掉了什么吗?

在Go语言中,结构体类型的方法可以修改结构体的数据。在这种情况下,Set() 方法是在 matrix 结构体上定义的,因此它可以访问和修改 matrix 结构体的数据字段 data。这是因为方法接收器是通过值传递的,所以在方法内部对接收器的修改不会影响原始的接收器值。但是,由于 matrix 结构体的数据字段是一个切片类型,切片是引用类型,所以对切片的修改会影响到原始的切片数据。因此,Set() 方法可以修改 data 切片中的元素值。

英文:
package matrix

import (
	&quot;errors&quot;
	&quot;strconv&quot;
	&quot;strings&quot;
)

// Matrix matrix inteface
type Matrix interface {
	Rows() [][]int
	Cols() [][]int
	Set(r, c, val int) bool
}

// matrix implements the interface Matrix
type matrix struct {
	data [][]int
	rows int
	cols int
}

// New returns a valid matrix created from the input
func New(input string) (Matrix, error) {
	var m matrix
	rows := strings.Split(input, &quot;\n&quot;)
	for r, row := range rows {
		rowElements := strings.Fields(row)

		switch {
		case r == 0:
			m.rows, m.cols = len(rows), len(rowElements)
			matrix, err := allocateMemory(m.rows, m.cols)
			if err != nil {
				return invalidMatrix()
			}
			m.data = matrix
		case len(rowElements) != m.cols:
			return invalidMatrix()
		}

		for c, element := range rowElements {
			element, err := strconv.Atoi(element)
			if err != nil {
				return invalidMatrix()
			}
			m.data[r][c] = element
		}
	}
	return m, nil
}

// invalidMatrix returns the error indicating the
// provided matrix is invalid
func invalidMatrix() (Matrix, error) {
	return nil, errors.New(&quot;invalid matrix&quot;)
}

// allocateMemory allocates a 2D slice of int having size RxC
func allocateMemory(R, C int) ([][]int, error) {
	if R &lt; 1 || C &lt; 1 {
		return nil, errors.New(&quot;invalid matrix&quot;)
	}
	matrix := make([][]int, R)
	for r := range matrix {
		matrix[r] = make([]int, C)
	}
	return matrix, nil
}

// Set sets the given value at (r,c) in the matrix,
// if (r,c) belongs to the matrix.
func (m matrix) Set(r, c, val int) bool {
	switch {
	case r &lt; 0 || c &lt; 0:
		return false
	case r &gt;= m.rows || c &gt;= m.cols:
		return false
	default:
		m.data[r][c] = val
		return true
	}
}

// order defines the order the matrix to export
// two useful values are columnMajor and rowMajor
type order int

const (
	columnMajor order = iota
	rowMajor
)

// Cols returns columns of the matrix.
func (m matrix) Cols() [][]int {
	return m.export(columnMajor)
}

// Rows returns rows of the matrix.
func (m matrix) Rows() [][]int {
	return m.export(rowMajor)
}

// export return the matrix in the required order;
// either columnMajor or rowMajor.
func (m matrix) export(o order) [][]int {
	var matrix [][]int
	var err error
	switch o {
	case columnMajor:
		matrix, err = allocateMemory(m.cols, m.rows)
		if err != nil {
			return nil
		}
		for r, row := range m.data {
			for c, element := range row {
				matrix[c][r] = element
			}
		}
	case rowMajor:
		matrix, err = allocateMemory(m.rows, m.cols)
		if err != nil {
			return nil
		}
		for r, row := range m.data {
			copy(matrix[r], row)
		}
	}
	return matrix
}

I am having a hard time understanding why the method Set() is able to modify the data of the struct. I had an understanding that methods defined on values cannot do that. I have tried to compare it with another problem where I cannot modify the content of receiver but in this case it just works. A test file for this code is available at test file. Any idea what I am missing?

答案1

得分: 6

Set能够修改切片的内容的原因是切片是一个引用值。你在评论中提到的另一个例子试图给持有切片的字段赋值,但这样做是行不通的,因为它是在操作一个副本。请看下面的代码示例:

package main

import (
	"fmt"
)

type Holder struct {
	s []int
	v []int
}

func (h Holder) Set() {
    // 这将成功地修改`s`切片的内容
	h.s[0] = 99

    // 这将给`v`字段的副本赋予一个新的切片,
    // 所以它不会影响到调用该方法的实际值。
	h.v = []int{1, 2, 3}
}

func main() {
	var h Holder
	h.s = []int{10, 20, 30}
	h.v = []int{40, 50, 60}

	fmt.Println("before Set:", h)
	
	h.Set()
	fmt.Println("after Set:", h)
}

你可以在 playground 上运行它,它会输出:

before Set: {[10 20 30] [40 50 60]}
after Set: {[99 20 30] [40 50 60]}

这里发生的情况是,尽管Set得到了h的一个副本,因此h.s也是一个副本,但是这两个副本指向同一个底层切片,所以可以修改其内容。阅读这篇文章获取更多细节信息。

英文:

The reason Set can modify the contents of the slice is that the slice is a reference value. Your other example (in the comment) attempts to assign the field holding the slice, and this won't work - because it's working on a copy. See this code sample:

package main
import (
&quot;fmt&quot;
)
type Holder struct {
s []int
v []int
}
func (h Holder) Set() {
// This will successfully modify the `s` slice&#39;s contents
h.s[0] = 99
// This will assign a new slice to a copy of the v field,
// so it won&#39;t affect the actual value on which this
// method is invoked. 
h.v = []int{1, 2, 3}
}
func main() {
var h Holder
h.s = []int{10, 20, 30}
h.v = []int{40, 50, 60}
fmt.Println(&quot;before Set:&quot;, h)
h.Set()
fmt.Println(&quot;after Set:&quot;, h)
}

You can run it on the playground, and it prints:

before Set: {[10 20 30] [40 50 60]}
after Set: {[99 20 30] [40 50 60]}

What happens here is that even though Set gets a copy of h, and hence h.s is a copy too, but both copies point to the same underlying slice, so the contents can be modified. Read this post for all the details.

答案2

得分: 3

一个切片值包含(ptr,len,cap),其中ptr是指向切片底层数组的指针。Set方法通过解引用指针修改切片的底层数组。存储在字段中的切片值不会被修改。

Go语言博客关于切片的文章更详细地描述了切片的内存布局。

英文:

A slice value contains (ptr, len, cap) where ptr is a pointer to the slice's underlying array. The Set method modifies the slice's underlying array by dereferencing the pointer. The slice value, stored in the field, is not modified.

The Go Language blog post on slices describes the slice memory layout in more detail.

huangapple
  • 本文由 发表于 2021年6月25日 07:37:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/68123796.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定