I'm following a book which shows the following example.
package main
import (
"fmt"
)
const (
KB = 1024
MB = 1048576 //KB * 1024
GB = 1073741824 //MB * 1024
TB = 1099511627776 //GB * 1024
PB = 1125899906842624 //TB * 1024
)
type ByteSize float64
func (b ByteSize) String() string {
switch {
case b >= PB:
return "Very Big"
case b >= TB:
return fmt.Sprintf("%.2fTB", b/TB)
case b >= GB:
return fmt.Sprintf("%.2fGB", b/GB)
case b >= MB:
return fmt.Sprintf("%.2fMB", b/MB)
case b >= KB:
return fmt.Sprintf("%.2fKB", b/KB)
}
return fmt.Sprintf("%dB", b)
}
func main() {
fmt.Println(ByteSize(2048))
fmt.Println(ByteSize(3292528.64))
}
When I run this program it gives me the following output (in human readable data size units).
2.00KB
3.14MB
But when I change the name of the function called String() to anything else, or if I lower-case the S in String, it gives me the following output.
2048
3.29252864e+06
What is the reason behind that? Is there some String() function attached to some interface and our ByteSize type satisfies that interface? I mean what the hell?
评论:
rauyran:
nevyn:Yes. Take a look at https://godoc.org/fmt#Stringer
avidal:Probably also worth linking to: https://gobyexample.com/interfaces
deusmetallum:That's exactly correct. There's an interface
fmt.Stringer
which, if implemented on your type, is used to get the string representation when callingfmt.Print
and family.
deusmetallum:It has to be called String() because fmt.Println is simply checking to see if that function exists. It can't check for all possible itterations!
Further more, anything beginning with a capitol letter is an "exported" function, meaning it can be run from any other package when imported. If you make the S lowercase, then the function can't be run in another module.
magpiecub:Also, if you change the name to string() or whatever(), you'd have to change your fmt.Println lines to look like this:
fmt.Println(ByteSize(2048).string())
Indu_Pillai:const ( KB = 1024 MB = 1048576 //KB * 1024 GB = 1073741824 //MB * 1024 TB = 1099511627776 //GB * 1024 PB = 1125899906842624 //TB * 1024 )
If
MB
isKB * 1024
as the comment suggests, why don't they just writeMB = KB * 1024
?
magpiecub:I changed that myself. Just cultivating the habit of reducing unwanted computations. :)
Indu_Pillai:Computers are fast and cheap. Humans are slow and expensive. You shouldn't be sacrificing readability to prevent 4 multiplication operations.
The operations don't even happen at run time - they happen at compile time:
% cat > main.go package main import "fmt" const x = 2 func main() { fmt.Println(x) } % go build -o x2 main.go % cat > main.go package main import "fmt" const x = 1 + 1 func main() { fmt.Println(x) } % go build -o x11 main.go % sha1sum x2 x11 515ccebaedfdedd5f3adba0d600b1d02873670b6 x2 515ccebaedfdedd5f3adba0d600b1d02873670b6 x11
zevdg:so it means the value of constants is calculated at the compile time?
rpk788:In recent versions of Go, yes. it's one of the many optimizations enabled by the recent SSA compiler work and is specifically called out in this video from Gophercon 2017.
That said, even if it weren't, you're talking about obfuscating your source code to gain fractions of nanoseconds at runtime. That's NEVER the right trade off unless you've profiled your code and found this particular path to be worth optimizing. Donald Knuth (who probably knows more about optimizing code than either of us ever will) famously said
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
And he said it again in this way
Programmers waste enormous amounts of time thinking about, or worrying about, the speed of noncritical parts of their programs, and these attempts at efficiency actually have a strong negative impact when debugging and maintenance are considered. We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%.
Please don't give into this evil. Always start by writing readable code and then if/when you need better performance, profile your code and only optimize the bottlenecks. Trust me, the people who you ask for help from (like us) will really appreciate it and be even be more likely to help if they can understand your code at a glance.
All of the other answers about implementing the Stringer interface are great. It is also worth mentioning that capitalization plays a big role in Go as well. so swapping the method name from String() to string() is more significant than you might think. See here:
