The learning materials are, as the title suggests, Effective Go on the website.
The most important part is the reinterpretation of slice (again, don’t create new tabs).
1. The format
You don’t have to do it yourself. There’s a Go tool for that. It’s called Gofmt. If you use vscode, you’ll find that vscode requires you to install a bunch of stuff. Which are basically commonly used Go tools. This includes gofmt, and vscode will configure you to automatically use gofmt to format your code every time you save. Similarly, Goimports automatically imports packages you forgot to import but used. If default, gofmt and other tools will be in your $GOPATH/bin. But a few details about GO:
- Using the TAB
- Try not to use parentheses
2. Comment
There’s nothing to say about the comment syntax itself. But it should be mentioned that goDoc is a tool that, like a Web server, documents the comments at the top of the functions in your source code. Vscode will not download this tool.
3. Naming rules
3.1 Naming methods of packages
3.1.1 The name of the package should be the name of the directory in which it resides.
Because of the various prefixes, the name of the package is not repeated, and much of the information is already stated on the prefixes. Therefore, the name of package should be short, preferably one word. For example, the base64 package in encoding should not be called encodingBase64, because the prefix tells you that it is in encoding, and you reference it by importing “encoding/base64”. If you call it encodingBase64, the actual reference will be import “encoding/encodingBase64”. It’s redundant and doesn’t fit with Go’s emphasis on simplifying everything.
3.1.2 Export type names should also be kept concise
For example, if you have a Reader type in the Bufio package, why not call it bufReader? Reader is used in both bufio.Reader and IO.Reader, so there is no ambiguity.
Similarly, the container/ring package contains a function that creates an instance, which takes an int and returns an instance of * ring. So what should we call it?
-
NewRing
-
New
Tip: There is only one ring type in the ring package. Answer: New, of course, but what if the ring packet has two types, one called ring and one called Necklace? At this point, I’m going to have to choose the first one, because it does conflict.
3.2 Getter/setter
If you have an unexported field called owner, the getter name should be:
-
Owner
-
GetOwner
Of course, if you have a setter, then you have to use SetOwner, which you can’t do.
3.3 interface
- If your interface has only one method:
- The name of the interface should be
< method name >+er
, such as
type dryer interface { dry() } Copy the code
- The name of the interface should be
- If there are more than one, suit yourself.
3.4 camelCase 还是 PascalCase
Just be consistent. The camelCase method is good except when you export the type you need to rely on the first letter to determine whether to export.
4. A semicolon
Go actually uses a semicolon to separate each command. The problem is that this is automatically added by the Go compiler, and you will get an error if you add it yourself. Therefore, you should never put the left side of the braces on a new line, because the previous line is automatically added with a semicolon, which causes an error:
if true // this is judged to be the end of the statement, and a semicolon is automatically added
{
// You can't use it like this
}
Copy the code
5. Control structure
-
Do not write unnecessary else (I am innocent), for example:
if a == 1 { return } fmt.Println(1) Copy the code
You can’t rewrite it like this because it’s redundant:
if a == 1 { return } else { fmt.Println(1)}Copy the code
-
Go does not allow reassignment, but you will often see this:
a, err := Method1() b, err := Method2() / /??? Err is already defined Copy the code
This is pure pragmatism in order not to name a whole bunch of err1,err2, etc., in the same scope, and can be reassigned if the following three conditions are met:
- Duplicate assignments of variables in the same scope (if the variable is declared in the outer scope, a new variable will be generated this time, that is, changing the new variable will not affect the outer variable)
- The new and old variable types remain unchanged.
- The second declaration has at least one newly assigned variable, so it doesn’t really work if it doesn’t have the new variable b.
You can almost see that this is syntax tailor-made for err.
-
Multiple if-else if-else constructs are best replaced by switch {}.
- A case in GO can be matched by a comma:
switch number { case 1.2.3.4.5: // do sth default: // do sth } Copy the code
- A case in GO can be matched by a comma:
-
The “break” in the switch will only break outside the switch. If there is a “for” loop, the only way to break out is with the tag:
Loop: for { switch cmd{ case cmd == "Jump out of the switch": break case cmd == "Break out of the for loop": break Loop } } Copy the code
-
Since the switch does not have a corresponding statement, just continue can break out of the outer for loop (unless you have two for loops, but continue can also be tagged, so it doesn’t matter).
5. The function
- Defer is often used to unlock, close channels, close files, and other forgettable actions.
6. Data
6.1 New Allocating memory
In GO, there are several ways to allocate memory. So let’s start with new.
-
Unlike other languages, new does not initialize. Instead, it allocates a zero memory space (not the size, but the value of the memory) to the parameter.
- For instance,
new(T)
What it does is it allocates a chunk of memory to T, which is full of zeros, and returns it*T
That’s the address of this block of memory.
- For instance,
-
What good does this do? This is useful because some data structures are created without using their values, but with their methods. For example, the mutex sync.mutex. Just need a new piece of memory can be used directly. No initialization is required.
- Of course you are
var v T
It’s the same, they’re not initialized, they’re both allocated a chunk of zero-valued memory. The difference is that var does not declare Pointers.
type MyLock struct { lock sync.Mutex } v := new(MyLock) // v is of type *MyLock var v MyLock // v is of type MyLock Copy the code
- Of course you are
6.2 Compound Literals
For example, mutex can be used in this way:
type SyncMap struct {
v map[string]string
mutex sync.mutex
}
sm := SyncMap{v: {"zouli": "shabi"}} // only v is initialized
// Even if mutex is not initialized, it can still be used because compound literals allocate memory. Mutex is actually zero.
Copy the code
6.3 make Allocating Memory
You don’t have a choice because you can only create slices, maps, channels, and vice versa. Because they have to be initialized. (Of course you can leave it uninitialized and then those values will be nil, but that doesn’t make sense.) The difference between make and other methods is:
- It returns T instead of *T. (Also because these three types are themselves reference types and do not require Pointers)
- Will initialize. (Because all three must be initialized to make sense)
6.4 an array
Arrays are not used that much in GO, they are used in Slice. But there are some features of arrays in GO that can be mentioned:
- Arrays in GO are value types, and when assigned to another array, the array copies all values.
- Pass an array as an argument, a copy, not a pointer.
- The size of the array is part of the type.
[10]int
和[20]int
Even different types.
Of course, you can always use ampersand to change the pointer. But as I said at the beginning, arrays are not very common.
6.5 slice
Slice encapsulates an array. Slice is used most of the time when arrays are needed. One big difference from arrays is that Slice already encapsulates a pointer. So there is no need to add & to pass the parameter and then change. Of course, as described in study note (3), the new slice created by Append requires a second-level pointer.
6.5.1 (important) I, Slice, explain (important)
To further explain this phenomenon, the source code for Slice looks like this:
As it appears, slice consists of a pointer (don’t get me wrong, array is just a name and type is pointer), len, and Cap ints. This pointer clearly points to the array behind slice, which has a size of CAP, whereas our slice only represents the length of Len.
- When creating a slice, a slice is returned instead of *slice because make is used. When we use
slice[0]
When you use this syntax, you’re actually operating on an array. - On top of that, there are a lot of online tutorials that are misleadingSaying slice is a pointer is bullshit. Slice is not a pointer, but a pointer inside slice. The syntax that you normally use for slice is translated by GO to operate on this pointer so it can be used as a pointer.
- This means that when you pass a slice argument to a function, it only copies 32 bytes (Pointers 8 bytes, two ints 8 bytes each (assuming you’re a 64-bit host), although it does pass values in.
- That is, the slices outside and inside the function are completely different slices, but thankfully the original array is the same, so changes can be synchronized.
- Another mislead is that Append doesn’t change Slice at all, but the original arrayAppend allocates a new block of memory, copies the value into it, and returns the address to array, overwriting its address. But slice itself is constant. It’s just that this property plus the fact that the function is passed by value these two properties together, which is thetaNew Slice + new array address“Is a completely new slice?
- So why do slice Pointers solve this problem? This is easy because using the slice pointer, we pass in the slice address, and even if the function copies the address, it points to the same slice! Great ~ :
- Strike while the iron is hot, make another pit here, see you step on it:
package main import "fmt" type Me struct { v int } func main(a) { m := Me{1} Test(&m) fmt.Printf("(%v, %T)", m, m) } func Test(m *Me) { m = &Me{3}}Copy the code
What is the output? :
- ({1}, main.Me)
- ({3}, main.Me)
You might think it’s number two, but it’s actually number one.
- Why?? I’m passing in a pointer to Me, and I’m using what I just learned about Slice.
- No, I don’t. Actually, all you have to do is put the
m = &Me{3}
to*m = Me{3}
Is a step in the right direction. - ????? Why? Don’t they mean the same thing? It’s always assigning addresses, right?
- No,
m = &Me{3}
It’s a process like this:- First, the m outside the function is passed in as &m, so it is passed in the address (say 0x00000). The go function always copies the value, so it copies the address again, again 0x00000, and assigns the value to the m inside the function. (Note: the value of m inside the function and the value of m outside the function are the same, but they are the same address, but the address itself is not the same, is not a bit convoluted, so please see the next item)
- The point here is that a pointer type is also a type, and the value is an address, right here
*Me
It’s a type, the ampersand outside the function and the m inside the function*Me
Type, that is to sayThere is a var m *Me = 0x00000 outside the function, and there is a var m *Me = 0x00000 inside the function
The address is just a value, not a pointer, but a value of type address. The m inside and outside of the function is different, it just happens to have the same value, and that value is the address. (Or, when passed in, a secondary pointer address has been created, which has not yet been assigned, but can be obtained by &m.) - So how to solve this problem? If you read the introduction above, you will automatically think of second-order Pointers. But using second-level Pointers is really redundant. Let’s first look at the correct way to do it, and then look at redundant writing of second-order Pointers.
- So let’s look at the correct syntax
*m = Me{3}
The clever thing here is not to use a double pointer, but to access the address directly, using*m
Find the memory that the address points to, then change the memory, and that’s it. In fact, almost nobody would make a mistake if it was c, but there’s a go pit involved here, which is that *m should be the correct use of the pointer. Because of the syntax sugar design of go, you get used to using m.v instead of (*m). V to change the value of v, but you will automatically use m in other operations, but for example in this assignment, because there is no syntax sugar, you can only write *m, but forget, so you make a mistake. - So let’s look at the secondary pointer:
- We even have to look at what can go wrong with second-order Pointers
His mistake was step two,*m2 = &Me{3}
, it:Becomes:
And step three is a lot more redundant because**m2
和*m
It’s the same thing. - The solution is to put
*m= &Me{3}
become**m = M{3}
And get rid of the last line, the last line is redundant at any time becausem2 = &m
It means from now on**m2
和*m
It’s the same thing. - I’ve come up with a point that I don’t want myself to go around all the time, that is, don’t
*m
As a pointer, it just means it’s a pointer when describing a type. but- Once the
*
When it appears on the right-hand side of an assignment (that is, an expression),*
It’s just an operator, and+
.-
The same. - Once the
*
Occurs on the left side of the assignment, then at this point*
And the pointer variable that follows it forms a new variable (not new, but old, which is the variable/value that this address points to).
- Once the
- We even have to look at what can go wrong with second-order Pointers