An array is a fixed-length sequence of zero or more elements of a particular type. Because of their fixed length, arrays are rarely used directly in Go. Slices, which can grow and shrink, are much more versatile, but to understand slices we must understand arrays first.
Individual array elements are accessed with the conventional subscript
notation, where subscripts run from zero to one less than the array length.
The built-in function len
returns the number of elements in the
array.
var a [3]int // array of 3 integers fmt.Println(a[0]) // print the first element fmt.Println(a[len(a)-1]) // print the last element, a[2] // Print the indices and elements. for i, v := range a { fmt.Printf("%d %d\n", i, v) } // Print the elements only. for _, v := range a { fmt.Printf("%d\n", v) }
By default, the elements of a new array variable are initially set to
the zero value for the element type, which is 0
for numbers.
We can use an
array literal to initialize an array with a list of values:
var q [3]int = [3]int{1, 2, 3} var r [3]int = [3]int{1, 2} fmt.Println(r[2]) // "0"
In an array literal, if an ellipsis “...
” appears in place of
the length, the array length is determined by the number of initializers.
The definition of q
can be
simplified to
q := [...]int{1, 2, 3} fmt.Printf("%T\n", q) // "[3]int"
The size of an array is part of its type, so [3]int
and [4]int
are different types. The size must
be a constant expression, that is, an expression whose value
can be computed as the program is being compiled.
q := [3]int{1, 2, 3} q = [4]int{1, 2, 3, 4} // compile error: cannot assign [4]int to [3]int
As we’ll see, the literal syntax is similar for arrays, slices, maps, and structs. The specific form above is a list of values in order, but it is also possible to specify a list of index and value pairs, like this:
type Currency int const ( USD Currency = iota EUR GBP RMB ) symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"} fmt.Println(RMB, symbol[RMB]) // "3 ¥"
In this form, indices can appear in any order and some may be omitted; as before, unspecified values take on the zero value for the element type. For instance,
r := [...]int{99: -1}
defines an array r
with 100 elements, all zero except for the last,
which has value −1.
If an array’s element type is comparable then the array type is
comparable too, so we may directly compare two arrays of that type
using the ==
operator, which reports whether all corresponding
elements are equal.
The !=
operator is its negation.
a := [2]int{1, 2} b := [...]int{1, 2} c := [2]int{1, 3} fmt.Println(a == b, a == c, b == c) // "true false false" d := [3]int{1, 2} fmt.Println(a == d) // compile error: cannot compare [2]int == [3]int
As a more plausible example, the function Sum256
in the crypto/sha256
package produces the SHA256 cryptographic hash or digest of a message
stored in an arbitrary byte slice.
The digest has 256 bits, so its type is [32]byte
. If two
digests are the same, it is extremely likely that the two messages are
the same; if the digests differ, the two
messages are different. This program prints and compares the SHA256 digests of "x"
and "X"
:
import "crypto/sha256" func main() { c1 := sha256.Sum256([]byte("x")) c2 := sha256.Sum256([]byte("X")) fmt.Printf("%x\n%x\n%t\n%T\n", c1, c2, c1 == c2, c1) // Output: // 2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881 // 4b68ab3847feda7d6c62c1fbcbeebfa35eab7351ed5e78f4ddadea5df64b8015 // false // [32]uint8 }
The two inputs differ by only a single bit, but approximately
half the bits are different in the digests.
Notice the Printf
verbs: %x
to print all the elements of an
array or slice of bytes in hexadecimal, %t
to show a boolean, and %T
to display the
type of a value.
When a function is called, a copy of each argument value is assigned to the corresponding parameter variable, so the function receives a copy, not the original. Passing large arrays in this way can be inefficient, and any changes that the function makes to array elements affect only the copy, not the original. In this regard, Go treats arrays like any other type, but this behavior is different from languages that implicitly pass arrays by reference.
Of course, we can explicitly pass a pointer to an array so that any
modifications the function makes to array elements will be visible to
the caller.
This function zeroes the contents of a [32]byte
array:
func zero(ptr *[32]byte) { for i := range ptr { ptr[i] = 0 } }
The array literal [32]byte{}
yields an array of 32 bytes.
Each element of the array has the zero value for byte
,
which is zero.
We can use that fact to write a different version of zero
:
func zero(ptr *[32]byte) { *ptr = [32]byte{} }
Using a pointer to an array is efficient and allows the called function to
mutate the caller’s variable, but arrays are still inherently
inflexible because of their fixed size. The zero
function will not
accept a pointer to a [16]byte
variable, for example, nor is there
any way to add or remove array elements. For these reasons,
other than special cases like SHA256’s fixed-size hash,
arrays are seldom used as function parameters; instead, we use
slices.
Exercise 4.1:
Write a function that counts the number of bits that are different in
two SHA256 hashes.
(See PopCount
from Section 2.6.2.)
Exercise 4.2: Write a program that prints the SHA256 hash of its standard input by default but supports a command-line flag to print the SHA384 or SHA512 hash instead.