reference

Effective Go – The Go Programming Language (google.cn)

Data | the Effective and efficient Go programming Go 2020 | Go technology BBS (learnku.com)

usenewAllocate memory

Go provides two allocation primitives, the built-in functions new and make. They do different things, they apply different types, and they can cause confusion, but the rules are simple. Let’s talk about new first. This is a built-in function that allocates memory, but it doesn’t initialize memory, it just sets it to zero.

The memory of the new initializer has been zeroed out, so when you’re dealing with data structures, there’s no need to do a single initialization for each type of zero. The bytes.buffer documentation, for example, states that “a zero-valued Buffer is a ready-to-use Buffer.” Similarly, sync.Mutex does not display a constructor Init method. Instead, a zero-value sync.Mutex is already an unlocked Mutex.

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}
Copy the code
// A Buffer is a variable-sized buffer of bytes with Read and Write methods.
// The zero value for Buffer is an empty buffer ready to use.
type Buffer struct {
   buf      []byte // contents are the bytes buf[off : len(buf)]
   off      int    // read at &buf[off], write at &buf[len(buf)]
   lastRead readOp // last read operation, so that Unread* can work correctly.
}
Copy the code

Values of type SyncedBuffer are also allocated memory ready at declaration time. In subsequent code, p and V work correctly without further processing.

func main() { p := new(SyncedBuffer) // type *SyncedBuffer var v SyncedBuffer // type SyncedBuffer fmt.Println(p) //&{{0  0} {[] 0 0}} fmt.Println(v) //{{0 0} {[] 0 0}} fmt.Println(&v == p) //false }Copy the code

Constructor and Composite literals

Sometimes zero is not so friendly, and we need to initialize a constructor

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := new(File)
    f.fd = fd
    f.name = name
    f.dirinfo = nil
    f.nepipe = 0
    return f
}
Copy the code

The assignment above is too verbose and is simplified by a Composite Literal.

func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    f := File{fd, name, nil.0}
    return &f
}
Copy the code

In rare cases, if a compound literal does not include any fields, it will create a zero value for that type. The expressions new(File) and &File{} are equivalent.

a := [...]string{"no error"."Eio"."invalid argument"}
s := []string{"no error"."Eio"."invalid argument"}
m := map[int]string{1: "no error".2: "Eio".3: "invalid argument"}
Copy the code

Use make to allocate memory

Use make(T, args) for a different purpose than new(T). It is only used to create slices, maps, and channels. And returns an initialized value of type T. The reason for this difference is that these three are essentially reference types that must be initialized before they can be used. For example, a slice is a slice that has three types, including a pointer to the data (inside the array), length, and capacity. The slice is nil until all three are initialized.

type slice struct {
   array unsafe.Pointer
   len   int
   cap   int
}
Copy the code
make([]int.10.100)
Copy the code

An array space of 100 ints is allocated, and a slice structure of length 10, capacity 100 is created that points to the first 10 elements of the array. (When generating slices, its capacity can be omitted; see the section on slices for more information.) In contrast, new([]int) returns a pointer to a newly allocated, zeroed slice structure, that is, a pointer to a nil slice value.

var p *[]int = new([]int)       // allocates slice structure; *p == nil; rarely useful
var v  []int = make([]int, 100) // the slice v now refers to a new array of 100 ints

// Unnecessarily complex:
var p *[]int = new([]int)
*p = make([]int, 100, 100)

// Idiomatic:
v := make([]int, 100)
Copy the code

Array

Arrays are useful when planning memory layouts in detail and sometimes avoiding excessive memory allocation, but they are primarily used as building blocks for slicing. That’s the subject of the next video, but just a few words to set it up. The following are the main differences between arrays in Go and C. In Go,

  • Arrays are values. Assigning one array to another copies all its elements.
  • In particular, if you pass an array into a function, it receives a copy of the array instead of a pointer.
  • The size of an array is part of its type. type[10]int 和 [20]intIt’s different.

Arrays of values are useful, but expensive; If you want the behavior and efficiency of C, you can pass a pointer to that array.

func Sum(a *[3]float64) (sum float64) {
    for _, v := range *a {
        sum += v
    }
    return
}

array := [...]float64{7.0.8.5.9.1}
x := Sum(&array)  // Note the explicit address-of operator
Copy the code

slice

Slicing provides a more general, powerful and convenient interface for data sequences by encapsulating arrays. Most array programming in Go is done by slicing, except for cases like matrix transformations that require dimensionality. Slicing holds references to the underlying array, and if you assign one slice to another, they all reference the same array. If a function passes a slice as an argument, its changes to that slice element are also visible to the caller, which can be interpreted as passing a pointer to the underlying array. Thus, the Read function can take a slice argument instead of a pointer and a count; The length of the slice determines the upper limit of the data that can be read. The following is the signature of the Read method of type File in the OS package:

func (f *File) Read(buf []byte) (n int, err error)
Copy the code

This method returns the number of bytes read and an error value, if any. To read the first 32 bytes from the larger buffer B, you simply slice it.

    n, err := f.Read(buf[0:32])
Copy the code

This method of slicing is common and efficient. Regardless of efficiency, the following fragment can also read the first 32 bytes of the buffer.

    var n int
    var err error
    for i := 0; i < 32; i++ {
        nbytes, e := f.Read(buf[i:i+1])  // Read one byte.
        n += nbytes
        if nbytes == 0|| e ! =nil {
            err = e
            break}}Copy the code

As long as the slice stays within the limits of the underlying array, its length is variable, simply by assigning it to its own slice. The capacity of the slice can be obtained by the built-in function CAP, which gives the maximum length that the slice can take. Here are the functions that append data to slices. If the data exceeds its capacity, the slice is reallocated. The return value is the resulting slice. The len and cap used in this function are legal when applied to nil slices, and it returns 0.

func Append(slice, data []byte) []byte {
    l := len(slice)
    if l + len(data) > cap(slice) {  // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2)
        // The copy function is predeclared and works for any slice type.
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:l+len(data)]
    copy(slice[l:], data)
    return slice
}
Copy the code

Two-dimensional slices

Arrays and slices of Go are one-dimensional. To create an equivalent two-dimensional array or slice, we must define an array of arrays, or slices of slices, like this:

type Transform [3] [3]float64  // A 3x3 array, really an array of arrays.
type LinesOfText [][]byte     // A slice of byte slices.
Copy the code
text := LinesOfText{
   []byte("Now is the time"),
   []byte("for all good gophers"),
   []byte("to bring some fun to the party."),
}

fmt.Println(text)
/*[[78 111 119 32 105 115 32 116 104 101 32 116 105 109 101] [102 111 114 32 97 108 108 32 103 111 111 100 32 103 111 [116 111 32 98 114 105 110 103 32 115 111 109 101 32 102 117 110 32 116 111 32 116 104 101 32 112 ] */
form := Transform{
   [3]float64{1.2.3},3]float64{4.5.6},3]float64{7.8.9},
}
fmt.Println(form)
//[1 2 3] [4 5 6] [7 8 9]]
Copy the code

Sometimes it is necessary to allocate a two-dimensional array, such as when dealing with scanned rows of pixels. We can do this in two ways.

// Allocate memory independently for each slice
func allocate(a) {
   // Allocate the top-level slice.
   picture := make([] []uint8, YSize) // One row per unit of y.
   fmt.Println(picture)//[[] [] [] [] [] [] [] [] [] [] []

   // Loop over the rows, allocating the slice for each row.
   for i := range picture {
      picture[i] = make([]uint8, XSize)
   }

   fmt.Println(picture)
   / / [[0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 0 0 0]]
}

// Apply for a large slice to which other slices point
func allocate2(a) {
   // Allocate the top-level slice, the same as before.
   picture := make([] []uint8, YSize) // One row per unit of y.
   // Allocate one large slice to hold all the pixels.
   pixels := make([]uint8, XSize*YSize) // Has type []uint8 even though picture is [][]uint8.
   // Loop over the rows, slicing each row from the front of the remaining pixels slice.
   for i := range picture {
      picture[i], pixels = pixels[:XSize], pixels[XSize:]
   }
}
Copy the code

Maps figure

A Map in Go is actually a HashTable in a data structure. Maps are convenient and powerful built-in data structures that can correlate different types of values. The keys can be any type supported by the equality operator, such as integers, floating-point numbers, complex numbers, strings, Pointers, interfaces (as long as their dynamic types support equality judgment), structures, and arrays. Slices cannot be used as mapping keys because their equality is not defined. A map, like a slice, is a reference type. If you pass a mapping into a function and change the content of the mapping, the modification is visible to the caller as well.

Maps can be built using normal compound literal syntax, with key-value pairs separated by colons, so they can be easily built at initialization.

var timeZone = map[string]int{
    "UTC":  0*60*60,
    "EST": -5*60*60,
    "CST": -6*60*60,
    "MST": -7*60*60,
    "PST": -8*60*60,
}
Copy the code

Assigning to a value in a map has a syntax similar to that of an array, except that its index is not necessarily an integer.

offset := timeZone["EST"]
Copy the code

An attempt to evaluate by a key that does not exist in the map returns a zero value corresponding to the type of the item in the map. For example, if a map contains integers, a search for a nonexistent key returns zero. The collection can be implemented as a mapping of value type bool. Setting the item in the map to true puts the value into the collection, which can then be determined by a simple indexing operation.

func mapDemo() {
   attended := map[string]bool{
      "Ann": true,
      "Joe": true,
   }

   if attended["Ann"] { // will be false if person is not in the map
      fmt.Println("Ann", "was at the meeting")
   }

   fmt.Println(attended)
}
Copy the code

Sometimes you need to distinguish between something that doesn’t exist and something that has a zero value. For example, a “UTC” entry that should have a value of zero may have a value of zero because the entry does not exist. You can distinguish this by using the form of multiple assignments.

var timeZone = map[string]int{
   "UTC":  0*60*60,
   "EST": -5*60*60,
   "CST": -6*60*60,
   "MST": -7*60*60,
   "PST": -8*60*60,
}

seconds, ok := timeZone["UTC"]
fmt.Println(seconds, ok)
//0 true

seconds, ok = timeZone["UFC"]
fmt.Println(seconds, ok)
//0 false
Copy the code

Obviously, we can call this a “comma OK” syntax. In the following example, if tz exists, seconds will be given the appropriate value and OK will be set to true; If not, seconds is set to zero and OK is set to false.

func offset(tz string) int {
    if seconds, ok := timeZone[tz]; ok {
        return seconds
    }
    log.Println("unknown time zone:", tz)
    return 0
}
Copy the code

If you want to determine the existence of an item in the map without caring about the actual value, you can use the blank identifier _ instead of the generic variable of that value.

_, present := timeZone[tz]
Copy the code

To delete an item in a mapping, use the built-in function delete, which takes the mapping and the key to be deleted. This operation is safe even if the corresponding key is not in the map.

delete(timeZone, "PDT")  // Now on Standard Time
Copy the code

Printing

Go’s formatted printf output style is the same as the C type, but it’s much richer. These functions are in the FMT package. Such as FMT.Printf, FMT.Fprintf, FMT.Sprintf. The string function (Sprintf) returns a string instead of populating the given buffer.

func printing() {
   fmt.Printf("Hello %d\n", 23)
   fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
   fmt.Println("Hello", 23)
   fmt.Println(fmt.Sprint("Hello ", 23))
}
Copy the code

A formatted print function such as FMT.Fprint accepts as its first argument any object that implements the IO.Writer interface. The variables os.stdout and os.stderr are well-known examples.

Numeric formats like %d do not accept symbols or size tokens, and the printing routine determines these attributes based on the type of argument.

var x uint64 = 1<<64 - 1
fmt.Printf("%d %x; %d %x\n", x, x, int64(x), int64(x))
// 18446744073709551615 ffffffffffffffff; -1 -1
Copy the code

If you only want the default conversion, such as decimal integers, you can use the generic format %v (for “value”); The result is exactly the same as Print and Println. In addition, this format can print arbitrary values, including arrays, structures, and mappings. Here is the statement that prints the time zone mapping defined in the previous section.

fmt.Printf("%v\n", timeZone)  // or just fmt.Println(timeZone)
Copy the code

The output

map[CST:-21600 EST:-18000 MST:-25200 PST:-28800 UTC:0]
Copy the code

For maps, Printf automatically sorts the map values in lexicographical order by key.

Of course, the keys in the map can be output in any order. When printing a structure, the improved format %+v adds field names to each field of the structure, while the other format %#v prints the values exactly as Go does.

Struct {a int b float64 c string} T := &t {7, -2.35, "ABC \tdef"} fpt.printf ("%v\n", T) fpt.printf ("%v\n", T) fpt.printf ("%v\n", T) fpt.printf ("%v\n", T) t) fmt.Printf("%#v\n", t) fmt.Printf("%#v\n", timeZone)Copy the code

The output

ABC def & {7-2.35} and {a: 7 b: 2.35 c: ABC def} & main. T {7, a: b: 2.35, c: "ABC \ tdef}" map [string] int {" CST ": - 21600. "EST":-18000, "MST":-25200, "PST":-28800, "UTC":0}Copy the code

(Note the ampersand.) When a string or []byte value is encountered, use %q to produce a quoted string; The format %#q uses backquotes whenever possible. (The %q format also works with integers and runes, and produces a single quoted rune constant.) In addition, %x can be used with strings, byte arrays, and integers and generates a long hexadecimal string, and the whitespace format (%x) inserts Spaces between bytes.

fmt.Printf("%qn", t) //&{'\a' %! Q (float64 = 2.35) "ABC \ tdef"} FMT. Printf (" # % q \ n, t) / / & {' \ a '%! Q (float64 = 2.35000) ` ABC def `} FMT) Printf (" % x \ n ", 17) / / 11Copy the code

%T outputs a value type

fmt.Printf("%T\n", timeZone)
//map[string]int
Copy the code

If you want to control the default format of a custom type, you simply define a method for that type with a String() String signature. For our simple type T, do the following.

Func t * (t) String String () {return FMT. Sprintf (" % d/g / % q % ", t.a, t.b, tc)} FMT) Printf (" \ n "% v, t) 7/2.35 / / /" ABC \ tdef"Copy the code

Sprintf calls the String method of type. So using Sprintf on String leads to infinite recursion

// Do not construct the String method by calling Sprintf, Type MyString func (m MyString) String() String {return fmt.sprintf ("MyString=%s", m) // Error: will recur forever. }Copy the code

The solution to this problem is simple: convert the argument to a basic string type, which does not have this method.

type MyString string
func (m MyString) String() string {
    return fmt.Sprintf("MyString=%s", string(m)) // OK: note conversion.
}
Copy the code

In the initialization section, we’ll see another technique for avoiding this recursion.

Another printing technique is to pass arguments to a printing routine directly to another such routine. Printf’s signature uses… for its final argument. Interface {} type, so that any number of parameters of any type can appear after the format.

func Printf(format string, v ... interface{}) (n int, err error) {Copy the code

There are many things about printing that have not been mentioned. See the GODoc documentation for FMT packages for details.

By the way… A parameter can specify a specific type, such as a function min that selects the smallest value from a list of integers. Its parameter can be… The int type.

func Min(a ... int) int { min := int(^uint(0) >> 1) // largest int for _, i := range a { if i < min { min = i } } return min }Copy the code

Append

Now we’ll supplement the design of the built-in function Append. The signature of the append function is different from that of our custom append function. Roughly speaking, it looks like this:

func append(slice []T, elements ... T) []TCopy the code

Where T is a placeholder for any given type. In fact, you can’t write a function in Go whose type T is determined by the caller. This is why Append is a built-in function: it requires compiler support. C# and Java support generics.

Append appends the element at the end of the slice and returns the result. We must return the result for the same reason as our handwritten Append, that the underlying array may be changed. Here’s a simple example

X: int [] = {1, 2, 3} x = append (x, 4, 5, 6) FMT. Println (x)Copy the code

Print [1, 2, 3, 4, 5, 6]. So append is a bit like Printf, accepting any number of arguments.

But what if we want to Append one slice to another as Append does? It’s simple: use… where called. , just as we called Output above. The output of the following code snippet is the same as the previous one.

X: int [] = {1, 2, 3} : y = [] int x = {4 and 6} append (x, y)... fmt.Println(x)Copy the code