golang 第三章 变量

变量使用注意事项

  1. 变量表示内存中的一个存储区域
  2. 该区域有自己的名称(变量名)和类型(数据类型)
  3. golang使用变量的3种方式:
    1. 指定变量类型,声明后若不赋值,则使用默认值
    func main()  {
        // int的默认值是0,其他数据类型的默认值在后面介绍
        var i int
        fmt.Println("i=",i)
    }
    
    1. 根据值自行判断变量类型(类型推导)
    func main()  {
        var i = 10.12
        fmt.Println("i=",i)
    }
    
    1. 省略var,:=左边的变量不应该是已经声明过的,否则会导致编译错误
    func main()  {
        i := "tom"
        fmt.Println("i=",i)
    }
    
  4. 多变量声明
    1. 一次性声明多个局部变量
    func main()  {
        // 方式1:
        // var x, y, z int
        // fmt.Println("x=",x, "y=",y, "z=",z)
    
        // 方式2:
        // var x, y, z = 100, "tom", 888
        // fmt.Println("x=",x, "y=",y, "z=",z)
    
        // 方法3:
        x, y, z := 100, "tom", 888
        fmt.Println("x=",x, "y=",y, "z=",z)
    }
    
    1. 一次性声明多个全局变量
    // 方法1:
    //var x = 100
    //var y = "tom"
    //var z = 888
    
    // 方法2:
    var(
        x = 100
        y = "tom"
        z = 888
    )
    
    func main()  {
        fmt.Println("x=",x, "y=",y, "z=",z)
    }
    
  5. 该区域的数据值可以在同一类型范围内不断变化
package main
import "fmt"
func main () {
    var i int =10
    i = 20
    i = 30
    fmt.Printf("i=",i)
    i = 1.2  // i的类型变为float,不能改变数据类型
}
  1. 变量在同一作用域(在一个函数或者代码块)内不能重名
func main () {
	var i int = 10
	i := 30 // 变量名称重复
}
  1. 变量=变量名+数据类型+(自定义值或者默认值)
  2. golang的变量如果没有赋初始值,编译器会使用默认值,比如int默认值为0、string默认值为空串、float默认值为0

变量的声明、初始化和赋值

  1. 声明变量
    基本语法var 变量名 数据类型或者var 变量名 := 变量初始值(自动推导出变量类型)
    例如var a int声明int类型的变量a,var num1 float32声明一个单精度类型的变量num1
  2. 初始化变量
    即在声明变量的时候就赋值
    例如var a int = 45,如果声明时就赋值,可以省略数据类型var a = 45
  3. 变量赋值
    声明变量时不初始化var a int,然后再赋值a = 45,即变量赋值

程序中“+”使用

  1. 当左右两边都是数值型时,则做加法运算
  2. 当左右两边都是字符串时,则做字符串拼接

数据类型基本介绍

数据类型分为两大类,基本数据类型和派生/复杂数据类型:

1. 基本数据类型

分类

  1. 数值型
    1. 整数类型
      1. int(有符号)
        1. int8,有符号,占1字节,范围-2^7~2^7-1
        2. int16,有符号,占2字节,范围-2^15~2^15-1
        3. int32,有符号,占4字节,范围-2^31~2^31-1
        4. int64,有符号,占8字节,范围-2^63~2^63-1
      2. uint(无符号)
        1. uint8,无符号,占1字节,范围0~2^8-1
        2. uint16,无符号,占2字节,范围0~2^16-1
        3. uint32,无符号,占4字节,范围0~2^32-1
        4. uint64,无符号,占8字节,范围0~2^64-1
      3. 常用类型说明
        1. int,有符号
          1. 32位系统占4字节,范围-2^31~2^31-1
          2. 64位系统占8字节,范围-2^63~2^63-1
        2. uint,无符号
          1. 32位系统占4字节,范围0~2^32-1
          2. 64位系统占8字节,范围0~2^64-1
        3. rune,等价int32,表示一个Unicode码
        4. byte,等价uint8,但需要存储字符时选用byte
    2. 浮点类型
      1. float32,单精度,占4字节
      2. float64,双精度,占8字节
  2. 字符型(没有专门的字符型,使用byte来保存的那个字母字符)
  3. 布尔型bool,只允许取值true或者false,占1字节,适用于逻辑运算,一般用于流程控制
  4. 字符串string

整型使用细节

  1. 整数类型分有符号和无符号,int和uint的大小和系统有关
  2. 整型默认声明为int型(即推导类型时,默认为int),例如var n1 = 100,中的变量n1的数据类型默认为int
  3. 查看某个变量的字节大小和数据类型
func main () {
	var n2 int64 = 10
    // %T查看变量的类型;unsafe.Sizeof()是unsafe包的一个函数,返回变量的占用字节数
	fmt.Printf("n2 的类型 %T , n2 占用的字节数 %d",n2,unsafe.Sizeof(n2))
}
  1. 整型变量在使用时,原则是保小不保大,即在保证程序正确运行下,尽量使用占用空间小的数据类型,例如年龄可以使用byte类型
  2. bit计算机中最小的存储单位,byte是计算机中存储的基本单元

浮点型使用细节

  1. 浮点数在机器中存放形式:浮点数=符号位+指数位+尾数位浮点数都是有符号的
  2. 尾数部分可能丢失,造成精度损失float64的精度比float32的要准确,如果要保存精度高的数据,则首选float64。通常情况下也建议使用float64
  3. 浮点类型有固定的范围和字段长度,不受具体OS的影响
  4. 浮点型默认声明位float64类型
  5. 浮点型常量有两种表示形式:十进制例如5.12和科学计数法例如5.12e2

字符型使用细节

golang中没有专门的字符串类型,如果要存储单个字符(字母),一般使用byte来保存。
字符串就是一串固定长度的字符串连接起来的字符序列。go的字符产是由单个字节连接起来的,也就是说对于传统的字符串是由字符组成的,而go的字符串不同,它是由字节组成的
byte类型的值使用的是单引号,而不是双引号。单引号代表byte,双引号是字符串string

func main () {
	var c1 byte = 'a'
	var c2 byte = '1'

	// 当直接输出byte值,输出的是对应字符的ASCII码值
	fmt.Printf("c1=",c1,"c2=",c2)

	// 如果希望输出对应的字符,则需要使用格式化输出
	fmt.Printf("\nc1=%c c2=%c\n",c1,c2)

	// var c3 byte = '我' //overflow溢出,“我”类型为rune(等价int32)
	var c3 int = '我'
	fmt.Printf("c3=%c c3对应的码值%d",c3,c3)
}
  1. 如果保存的字符在ASCII码表中,比如0-9、a-z、A-Z直接可以保存为byte;如果对应的码值大于255,则需要使用int类型保存;如果需要输出字符而不是码值,则需要进行格式化输出,即fmt.Printf("%c",c1...)
  2. 字符常量是用单引号''括起来的单个字符,例如var c1 byte = 'a'var c2 int = '中'var c3 byte = '9'
  3. golang使用utf-8编码,查询字符对应的utf-8编码http://www.mytju.com/classcode/tools/encode_utf8.asp英文字符占1字节,中文字符占3字节
  4. 字符的本质是一个整数,所以直接输出时,输出的是该字符对应的utf-8编码的码值
  5. 可以直接给某个变量赋一个数字,然后按格式化输出%c,会输出该数字对应的unicode字符
  6. 字符类型是可以进行运算的,相当于一个整数,因为它对应有Unicode
  7. 字符型本质:
    1. 字符型存储到计算机中,需要将字符对应的码值(整数)找出来
      1. 存储:字符--对应码值--二进制--存储
      2. 读取:二进制--码值--字符--读取
    2. 字符和码值的对应关系是通过字符编码表决定的
    3. go语言的编码都统一成utf8,非常方便

字符串使用细节

  1. golang的字符串的字节使用utf-8编码表示Unicode文本,不会再有中文乱码的问题
  2. 字符串一旦赋值,就无法修改了,即在golang中字符串时不可变的
func main () {
	var c1 string = "abcdef"
	c1[0] = 'x'  //无法修改字符串内容,提示报错
	fmt.Printf("c1= %s",c1)
}
  1. 字符串的两种表示形式
    1. 双引号,会识别转义字符,例如"abc\n123"
    2. 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果,类似linux的shell中的单引号
  2. 字符串拼接方式
func main () {
	var c1 = "hello " + "world"
	c1 += " haha!"
	fmt.Printf("c1= %s",c1)
}
  1. 当一行字符产太长时,需要使用到多行字符串,可以分行写,但是+号必须保留在上一行
func main () {
	var c1 = "hello " + "world" +
	" haha!"
	fmt.Printf("c1= %s",c1)
}

2. 派生/复杂数据类型

分类

  1. 指针pointer
  2. 数组
  3. 结构体struct
  4. 管道channel
  5. 函数
  6. 切片slice
  7. 接口interface
  8. map

基本数据类型的默认值

数据类型 默认值
整型int 0
浮点型float 0.0
字符串string ""
布尔型bool false
func main () {
	var a int
	var b float32
	var c float64
	var d bool
	var e string
	// %v 表示按照变量的值输出
	fmt.Printf("默认值 int=%d float32=%v float64=%v bool=%v string=%v",a,b,c,d,e)
}

// 默认值 int=0 float32=0 float64=0 bool=false string=

基本数据类型的相互转换

golang在不同类型的变量之间赋值时需要显式转换,即golang中数据类型不能自动转换
转换方式:T(v),表示将值v转换成类型T

func main () {
	var a int32 =100
	var b float32 = float32(a)
	var c int8 = int8(a)
	var d int64 = int64(a)
	var e string = string(a)
	// %v 表示按照变量的值输出
	fmt.Printf("数据类型转换 int32=%v float32=%v int8=%v int64=%v string=%v",a,b,c,d,e)
}

//数据类型转换 int32=100 float32=100 int8=100 int64=100 string=d

由上可见:

  1. 数据类型转换,可以从精度高<-->精度低 或者 范围大<-->范围小进行双向转换。但是在转换时需要考虑数据类型所能表示的范围,否则由范围大转向范围小的类型例如int64转换成int8,在编译时不会报错,只是转换的结果按照溢出处理
  2. 被转换的是变量存储的数据,变量本身的数据类型并没有变化原因是同一个变量无法进行第2次声明,即第一次声明之后其类型固定
  3. 不同类型的数据不能相加,需要先转成同一类型之后才能进行相加操作
// 溢出处理:
func main () {
	var a int64 = 9999
	var b int8 = int8(a)

	// %v 表示按照变量的值输出
	fmt.Printf("b= %v",b)
}

//b= 15

//--------------------------------------

// 先转换类型再操作:
func main () {
	var a int32 = 10
	var b int64
	var c int8

	b = int64(a) + 1  // b=a+1会报错,因为b和a数据不同数据类型
	c = int8(b) + 2

	// %v 表示按照变量的值输出
	fmt.Printf("b=%v c=%v",b,c)
}

// b=11 c=13

基本数据类型和string之间转换

基本数据类型-->string

  1. 方法1(更通用更灵活)
    fmt.Sprintf("%参数",表达式),Sprintf根据format参数生成格式化的字符串并返回该字符串
  2. 方法2
    使用strconv包的函数,大致的格式为strconv.FormatXXX()
import (
	"fmt"
	"strconv"
)

func main () {
	// 第一种方式
	var num1 int = 99
	var num2 float64 = 23.456
	var b bool = true
	var myChar byte = 'h'
	var str string //空的str

	//使用第一种方式来转换 fmt.Sprintf方法

	str = fmt.Sprintf("%d", num1)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="99"

	str = fmt.Sprintf("%f", num2)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="23.456000"

	str = fmt.Sprintf("%t", b)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="true"

	str = fmt.Sprintf("%c", myChar)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="h"

	
	//第二种方式 strconv 函数
	var num3 int = 99
	var num4 float64 = 23.456
	var num5 int64 = 4567
	var b2 bool = true

	str = strconv.FormatInt(int64(num3), 10)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="99"

	// strconv.FormatFloat(num4, 'f', 10, 64)
	// 说明: 'f' 格式 10:表示小数位保留10位 64 :表示这个小数是float64
	str = strconv.FormatFloat(num4, 'f', 10, 64)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="23.4560000000"

	str = strconv.FormatBool(b2)
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="true"

	//strconv包中有一个函数Itoa
	str = strconv.Itoa(int(num5))
	fmt.Printf("str type %T \tstr=%q\n", str, str)	// str type string 	str="4567"
}

string-->基本数据类型

使用strconv包的函数,大致的格式为strconv.ParseXXX()
注意事项:

  1. 默认返回的数据类型为int64或者float64,想转成其他数据类型则需要进一步转换
  2. 将string转换成基本数据类型时,需要确保string类型能够转换成有效的数据,比如可以把“123”转换成整数,但是无法将“hello”转换成整数,此时golang直接将其转换成0,其他类型类似,float为0、bool为false
import (
	"fmt"
	"strconv"
)

func main () {

	var str string = "true"
	var b bool
	// b, _ = strconv.ParseBool(str)
	// 说明
	// 1. strconv.ParseBool(str) 函数会返回两个值 (value bool, err error)
	// 2. 只想获取到 value bool ,不想获取 err ,所以使用_忽略
	b , _ = strconv.ParseBool(str)
	fmt.Printf("b type %T  b=%v\n", b, b)	// b type bool  b=true


	var str2 string = "1234590"
	var n1 int64
	var n2 int
	n1, _ = strconv.ParseInt(str2, 10, 64)
	n2 = int(n1)
	fmt.Printf("n1 type %T  n1=%v\n", n1, n1)	// n1 type int64  n1=1234590
	fmt.Printf("n2 type %T n2=%v\n", n2, n2)	// n2 type int n2=1234590


	var str3 string = "123.456"
	var f1 float64
	f1, _ = strconv.ParseFloat(str3, 64)
	fmt.Printf("f1 type %T f1=%v\n", f1, f1)  // f1 type float64 f1=123.456

    // 因为返回的只能是int64或者float64,如果希望得到int16、int32、float32等基本数据类型的数据,则需要进一步转换处理:
    // var f2 float32
    // f2 = float32(f1)


	//注意:
	var str4 string = "hello"
	var n3 int64 = 11
	n3, _ = strconv.ParseInt(str4, 10, 64)
	fmt.Printf("n3 type %T n3=%v\n", n3, n3)  // n3 type int64 n3=0

}

指针

基本介绍

  1. 基本数据类型,变量存的就是值,也叫值类型
  2. 值类型都有对应的指针类型,形式为*数据类型,比如int对应的指针类型就是*int
  3. 使用&获取变量的地址,比如var num int,获取变量num的地址方式为&num
func main () {
	var str int = 100
	fmt.Printf("str地址=",&str)	// str地址=%!(EXTRA *int=0xc000012078)
}
  1. 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,例如var ptr *int = &num
  2. 获取指针类型变量所指向的值,使用*,例如*ptr获取ptr指向的值。下面的代码详细说明了指针在内存中的布局情况
func main () {
	//基本数据类型在内存布局
	var i int = 10
	// i 的地址是什么,&i
	fmt.Println("i的地址=", &i)	//i的地址= 0xc0000ac058

	//下面的 var ptr *int = &i
	//1. ptr 是一个指针变量
	//2. ptr 的类型 *int
	//3. ptr 本身的值&i
	var ptr *int = &i
	fmt.Printf("ptr=%v\n", ptr)	//ptr=0xc0000ac058
	fmt.Printf("ptr 的地址=%v\n", &ptr)	//ptr 的地址=0xc0000d8020
	fmt.Printf("ptr 指向的值=%v\n", *ptr)	//ptr 指向的值=10

	// 修改i在内存中的值
	*ptr = 99
	fmt.Printf("i 修改之后内存中的值=%v", *ptr)	//i 修改之后内存中的值=99
}
指针类型

为什么要存在指针?
因为变量的值在声明过程中或者赋值过程中已经确定,之后无法进行修改。如果想要修改该变量的值,则需要直接进行内存级别的操作,修改内存中的值,首先需要获取这个变量存储的值在内存中的地址,然后才能进行内存操作,这个内存中的地址就是指针。
简而言之,指针存在的意义就是为了对值类型变量进行操作

值类型和引用类型

值类型

  1. 包含基本数据类型(int系列、float系列、bool、string)、数组、结构体struct
  2. 变量直接存储值,内存通常在中分配
    值类型

引用类型

包含指针、切片slice、map、管道chan、interface等
变量存储的是一个地址,这个地址对应的空间才真正存储数据值,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
引用类型
内存中栈区和堆区示意图

标识符

标识符命名规则

  1. 由0-9、a-z、A-Z、_组成,且开头不可以是数字
  2. golang中严格区分大小写,且标识符中不能包含空格
  3. 下划线_本身在go中是一个特殊的标识符,称为空标识符,可以代表任何其他标识符,但是它对应的值会被忽略(例如,忽略某个返回值时可以使用_),因此,仅能作为占位符使用,无法作为标识符使用
  4. 不能以系统保留关键字(25个)和预定义标识符(36个)作为自定义标识符,系统保留关键字和预定义标识符如下:
    1. 系统保留关键字简化代码编译过程中对代码的解析
      1. break:强制退出循环或switch语句块。跳出当前循环、switch、select,switchselect默认break,如果不希望break,使用fallthrough,以上这些结构可以用标签命名,break+标签名,可以跳出多层结构
      2. case:用于switch和select语句块中,后跟一个表达式,表示一个待匹配项
      3. chan:用于定义channel的关键字,,要用于不同协程直接通信,类似linux管道
      4. const:常量关键字
      5. continue:跳过本轮循环,进入下一轮循环迭代。continue也可以使用标签,忽略当前循环结构,直接继续执行标签指定的循环
      6. default:switch和switch语句块中定义的默认项
      7. defer:延迟调用关键字。在方法结束(包括异常)时调用,同一个方法内可以定义多个defer,调用顺序和定义相反。注意,defer后跟的表达式是定义时计算的,不是调用时计算的:
      func Defer() {
          i := 1
          defer println("first", i+1)
          i += 1
          defer println("second", i)
      }
      //输出
      //second 2 
      //first 2
      
      1. else:选择判断语句关键字
      2. fallthrough:switch语句块中使一个case子块执行完毕后,继续执行下一个case子块的代码
      3. for:循环体语句块关键字
      4. func: 定义函数和方法的关键字
      5. go:开启goroutine协程,后面直接跟方法调用
      6. goto:流程控制关键字,可以讲程序执行流程跳转到一个精确的位置
      7. if:选择判断语句关键字
      8. import:导入包,golang目前是不支持循环引用的,例如A import B、B 不能再引用A,A import B、B import C、C import A 也是不可以的
      9. interface:接口类型
      10. map:内置map类型,哈希字典
      11. package:定义包名
      12. range:定义迭代范围,常用在循环语句块中
      13. return:结束函数指定,使函数返回
      14. select: 用于定义通信控制结构的关键字
      15. struct: 结构体结构关键字
      16. switch:多重选择判断结构语句块关键字
      17. type:自定义类型关键字,即声明结构体、接口、类型和类型别名。声明一个类型,是作为一个新的类型使用,虽然本质上和原类型没有区别,但是无法直接当作原类型使用,而别名和原类型使用起来完全相同
      type A = int //int的别名
      type B int	//新类型b
      
      var a A = 1
      var b B = 1
      
      func GetSize(i int) {
          println(unsafe.Sizeof(i))
      }
      //64位机
      GetSize(a)	//输出8
      GetSize(b)	//无法通过编译,不能直接把B类型当作int使用
      GetSize(int(b))	//输出8,可以强制转换
      GetSize(*(*int)(unsafe.Pointer(&b)))//输出8,Pointer当然也是可以的
      
      1. var:变量定义关键字
    2. 预定义标识符包括基础数据类型和系统内嵌函数
      1. 内建常量: true false iota nil
      2. 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 complex128 bool byte rune string error
      3. 内建函数: make len cap new append copy close delete complex real imag panic recover

fmt(格式化IO)

fmt包实现了类似C语言printf和scanf的格式化I/O,格式化动作('verb')源自C语言但更简单。

type user struct {
    name string
}

func main() {
    u := user{"tang"}
    //Printf 格式化输出
    fmt.Printf("% + v\n", u)     //格式化输出结构
    fmt.Printf("%#v\n", u)       //输出值的 Go 语言表示方法
    fmt.Printf("%T\n", u)        //输出值的类型的 Go 语言表示
    fmt.Printf("%t\n", true)     //输出值的 true 或 false
    fmt.Printf("%b\n", 1024)     //二进制表示
    fmt.Printf("%c\n", 11111111) //数值对应的 Unicode 编码字符
    fmt.Printf("%d\n", 10)       //十进制表示
    fmt.Printf("%o\n", 8)        //八进制表示
    fmt.Printf("%q\n", 22)       //转化为十六进制并附上单引号
    fmt.Printf("%x\n", 1223)     //十六进制表示,用a-f表示
    fmt.Printf("%X\n", 1223)     //十六进制表示,用A-F表示
    fmt.Printf("%U\n", 1233)     //Unicode表示
    fmt.Printf("%b\n", 12.34)    //无小数部分,两位指数的科学计数法6946802425218990p-49
    fmt.Printf("%e\n", 12.345)   //科学计数法,e表示
    fmt.Printf("%E\n", 12.34455) //科学计数法,E表示
    fmt.Printf("%f\n", 12.3456)  //有小数部分,无指数部分
    fmt.Printf("%g\n", 12.3456)  //根据实际情况采用%e或%f输出
    fmt.Printf("%G\n", 12.3456)  //根据实际情况采用%E或%f输出
    fmt.Printf("%s\n", "wqdew")  //直接输出字符串或者[]byte
    fmt.Printf("%q\n", "dedede") //双引号括起来的字符串
    fmt.Printf("%x\n", "abczxc") //每个字节用两字节十六进制表示,a-f表示
    fmt.Printf("%X\n", "asdzxc") //每个字节用两字节十六进制表示,A-F表示
    fmt.Printf("%p\n", 0x123)    //0x开头的十六进制数表示
}

格式化动作分类

  1. 通用
%v	值的默认格式表示
%+v	类似%v,但输出结构体时会添加字段名
%#v	值的Go语法表示
%T	值的类型的Go语法表示
%%	百分号
  1. 布尔值
%t	单词true或false
  1. 整数
%b	表示为二进制
%c	该值对应的unicode码值
%d	表示为十进制
%o	表示为八进制
%q	该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
%x	表示为十六进制,使用a-f
%X	表示为十六进制,使用A-F
%U	表示为Unicode格式:U+1234,等价于"U+%04X"

没有%u。整数如果是无符号类型自然输出也是无符号的
类似的,也没有必要指定操作数的尺寸(int8,int64)

宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。
精度通过(可选的)宽度后跟点号后跟的十进制数指定。
如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。例如:
%f:    默认宽度,默认精度
%9f    宽度9,默认精度
%.2f   默认宽度,精度2
%9.2f  宽度9,精度2
%9.f   宽度9,精度0    

宽度和精度格式化控制的是Unicode码值的数量(不同于C的printf,它的这两个因数指的是字节的数量)。
两者任一个或两个都可以使用'*'号取代,此时它们的值将被对应的参数(按'*'号和verb出现的顺序,即控制其值的参数会出现在要表示的值前面)控制,这个操作数必须是int类型。

对于大多数类型的值,宽度是输出字符数目的最小数量,如果必要会用空格填充。
对于字符串,精度是输出字符数目的最大数量,如果必要会截断字符串。
对于整数,宽度和精度都设置输出总长度。采用精度时表示右对齐并用0填充,而宽度默认表示用空格填充。
对于浮点数,宽度设置输出总长度;精度设置小数部分长度(如果有的话),除了%g和%G,此时精度设置总的数字个数。例如,对数字123.45,格式%6.2f 输出123.45;格式%.4g输出123.5。%e和%f的默认精度是6,%g的默认精度是可以将该值区分出来需要的最小数字个数。
对复数,宽度和精度会分别用于实部和虚部,结果用小括号包裹。因此%f用于1.2+3.4i输出(1.200000+3.400000i)。
  1. 浮点数与复数
%b	无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e	科学计数法,如-1234.456e+78
%E	科学计数法,如-1234.456E+78
%f	有小数部分但无指数部分,如123.456
%F	等价于%f
%g	根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G	根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
  1. 字符串和字符
%s	直接输出字符串或者[]byte
%q	该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x	每个字节用两字符十六进制数表示(使用a-f)
%X	每个字节用两字符十六进制数表示(使用A-F) 
  1. 指针
%p	表示为十六进制,并加上前导的0x    
  1. 其他flag
'+'	总是输出数值的正负号;对%q(%+q)会生成全部是ASCII字符的输出(通过转义);
' '	对数值,正数前加空格而负数前加负号;
'-'	在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐);
'#'	切换格式:
  	八进制数前加0(%#o),十六进制数前加0x(%#x)或0X(%#X),指针去掉前面的0x(%#p);
 	对%q(%#q),如果strconv.CanBackquote返回真会输出反引号括起来的未转义字符串;
 	对%U(%#U),输出Unicode格式后,如字符可打印,还会输出空格和单引号括起来的go字面值;
  	对字符串采用%x或%X时(% x或% X)会给各打印的字节之间加空格;
'0'	使用0而不是空格填充,对于数值类型会把填充的0放在正负号后面;