变量使用注意事项
- 变量表示内存中的一个存储区域
- 该区域有自己的名称(变量名)和类型(数据类型)
- golang使用变量的3种方式:
- 指定变量类型,声明后若不赋值,则使用默认值
func main() { // int的默认值是0,其他数据类型的默认值在后面介绍 var i int fmt.Println("i=",i) }
- 根据值自行判断变量类型(类型推导)
func main() { var i = 10.12 fmt.Println("i=",i) }
- 省略var,
:=
左边的变量不应该是已经声明过的,否则会导致编译错误
func main() { i := "tom" fmt.Println("i=",i) }
- 多变量声明
- 一次性声明多个局部变量
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: //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) }
- 该区域的数据值可以在同一类型范围内不断变化
package main
import "fmt"
func main () {
var i int =10
i = 20
i = 30
fmt.Printf("i=",i)
i = 1.2 // i的类型变为float,不能改变数据类型
}
- 变量在同一作用域(在一个函数或者代码块)内不能重名
func main () {
var i int = 10
i := 30 // 变量名称重复
}
- 变量=变量名+数据类型+值(自定义值或者默认值)
- golang的变量如果没有赋初始值,编译器会使用默认值,比如int默认值为0、string默认值为空串、float默认值为0
变量的声明、初始化和赋值
- 声明变量
基本语法var 变量名 数据类型
或者var 变量名 := 变量初始值(自动推导出变量类型)
例如var a int
声明int类型的变量a,var num1 float32
声明一个单精度类型的变量num1 - 初始化变量
即在声明变量的时候就赋值
例如var a int = 45
,如果声明时就赋值,可以省略数据类型var a = 45
- 变量赋值
声明变量时不初始化var a int
,然后再赋值a = 45
,即变量赋值
程序中“+”使用
- 当左右两边都是数值型时,则做加法运算
- 当左右两边都是字符串时,则做字符串拼接
数据类型基本介绍
数据类型分为两大类,基本数据类型和派生/复杂数据类型:
1. 基本数据类型
分类
- 数值型
- 整数类型
- int(有符号)
- int8,有符号,占1字节,范围
-2^7~2^7-1
- int16,有符号,占2字节,范围
-2^15~2^15-1
- int32,有符号,占4字节,范围
-2^31~2^31-1
- int64,有符号,占8字节,范围
-2^63~2^63-1
- int8,有符号,占1字节,范围
- uint(无符号)
- uint8,无符号,占1字节,范围
0~2^8-1
- uint16,无符号,占2字节,范围
0~2^16-1
- uint32,无符号,占4字节,范围
0~2^32-1
- uint64,无符号,占8字节,范围
0~2^64-1
- uint8,无符号,占1字节,范围
- 常用类型说明
- int,有符号
- 32位系统占4字节,范围
-2^31~2^31-1
- 64位系统占8字节,范围
-2^63~2^63-1
- 32位系统占4字节,范围
- uint,无符号
- 32位系统占4字节,范围
0~2^32-1
- 64位系统占8字节,范围
0~2^64-1
- 32位系统占4字节,范围
- rune,等价int32,表示一个Unicode码
- byte,等价uint8,但需要存储字符时选用byte
- int,有符号
- int(有符号)
- 浮点类型
- float32,单精度,占4字节
- float64,双精度,占8字节
- 整数类型
- 字符型(没有专门的字符型,使用byte来保存的那个字母字符)
- 布尔型bool,只允许取值true或者false,占1字节,适用于逻辑运算,一般用于流程控制
- 字符串string
整型使用细节
- 整数类型分有符号和无符号,int和uint的大小和系统有关
- 整型默认声明为int型(即推导类型时,默认为int),例如
var n1 = 100
,中的变量n1的数据类型默认为int - 查看某个变量的字节大小和数据类型
func main () {
var n2 int64 = 10
// %T查看变量的类型;unsafe.Sizeof()是unsafe包的一个函数,返回变量的占用字节数
fmt.Printf("n2 的类型 %T , n2 占用的字节数 %d",n2,unsafe.Sizeof(n2))
}
- 整型变量在使用时,原则是保小不保大,即在保证程序正确运行下,尽量使用占用空间小的数据类型,例如年龄可以使用byte类型
- bit计算机中最小的存储单位,byte是计算机中存储的基本单元
浮点型使用细节
- 浮点数在机器中存放形式:浮点数=符号位+指数位+尾数位
浮点数都是有符号的
- 尾数部分可能丢失,造成精度损失
float64的精度比float32的要准确,如果要保存精度高的数据,则首选float64。通常情况下也建议使用float64
- 浮点类型有固定的范围和字段长度,不受具体OS的影响
- 浮点型默认声明位float64类型
- 浮点型常量有两种表示形式:十进制例如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)
}
- 如果保存的字符在ASCII码表中,比如0-9、a-z、A-Z直接可以保存为byte;如果对应的码值大于255,则需要使用int类型保存;如果需要输出字符而不是码值,则需要进行格式化输出,即
fmt.Printf("%c",c1...)
- 字符常量是用单引号
''
括起来的单个字符,例如var c1 byte = 'a'
、var c2 int = '中'
、var c3 byte = '9'
- golang使用utf-8编码,查询字符对应的utf-8编码http://www.mytju.com/classcode/tools/encode_utf8.asp
英文字符占1字节,中文字符占3字节
- 字符的本质是一个整数,所以直接输出时,输出的是该字符对应的utf-8编码的码值
- 可以直接给某个变量赋一个数字,然后按格式化输出
%c
,会输出该数字对应的unicode字符 - 字符类型是可以进行运算的,相当于一个整数,因为它对应有Unicode
- 字符型本质:
- 字符型存储到计算机中,需要将字符对应的码值(整数)找出来
- 存储:字符--对应码值--二进制--存储
- 读取:二进制--码值--字符--读取
- 字符和码值的对应关系是通过字符编码表决定的
- go语言的编码都统一成utf8,非常方便
- 字符型存储到计算机中,需要将字符对应的码值(整数)找出来
字符串使用细节
- golang的字符串的字节使用utf-8编码表示Unicode文本,不会再有中文乱码的问题
- 字符串一旦赋值,就无法修改了,即在golang中字符串时不可变的
func main () {
var c1 string = "abcdef"
c1[0] = 'x' //无法修改字符串内容,提示报错
fmt.Printf("c1= %s",c1)
}
- 字符串的两种表示形式
- 双引号,会识别转义字符,例如
"abc\n123"
- 反引号,以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果,类似linux的shell中的单引号
- 双引号,会识别转义字符,例如
- 字符串拼接方式
func main () {
var c1 = "hello " + "world"
c1 += " haha!"
fmt.Printf("c1= %s",c1)
}
- 当一行字符产太长时,需要使用到多行字符串,可以分行写,但是
+
号必须保留在上一行
func main () {
var c1 = "hello " + "world" +
" haha!"
fmt.Printf("c1= %s",c1)
}
2. 派生/复杂数据类型
分类
- 指针pointer
- 数组
- 结构体struct
- 管道channel
- 函数
- 切片slice
- 接口interface
- 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
由上可见:
- 数据类型转换,可以从精度高<-->精度低 或者 范围大<-->范围小进行双向转换。但是在转换时需要考虑数据类型所能表示的范围,否则由范围大转向范围小的类型例如int64转换成int8,在编译时不会报错,只是转换的结果按照溢出处理
- 被转换的是变量存储的数据,变量本身的数据类型并没有变化
原因是同一个变量无法进行第2次声明,即第一次声明之后其类型固定
- 不同类型的数据不能相加,需要先转成同一类型之后才能进行相加操作
// 溢出处理:
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(更通用更灵活)
fmt.Sprintf("%参数",表达式)
,Sprintf根据format参数生成格式化的字符串并返回该字符串 - 方法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()
注意事项:
- 默认返回的数据类型为int64或者float64,想转成其他数据类型则需要进一步转换
- 将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
}
指针
基本介绍
- 基本数据类型,变量存的就是值,也叫值类型
- 值类型都有对应的指针类型,形式为
*数据类型
,比如int对应的指针类型就是*int
- 使用
&
获取变量的地址,比如var num int
,获取变量num的地址方式为&num
func main () {
var str int = 100
fmt.Printf("str地址=",&str) // str地址=%!(EXTRA *int=0xc000012078)
}
- 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,例如
var ptr *int = &num
- 获取指针类型变量所指向的值,使用
*
,例如*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
}
为什么要存在指针?
因为变量的值在声明过程中或者赋值过程中已经确定,之后无法进行修改。如果想要修改该变量的值,则需要直接进行内存级别的操作,修改内存中的值,首先需要获取这个变量存储的值在内存中的地址,然后才能进行内存操作,这个内存中的地址就是指针。
简而言之,指针存在的意义就是为了对值类型变量进行操作
值类型和引用类型
值类型
- 包含基本数据类型(int系列、float系列、bool、string)、数组、结构体struct
- 变量直接存储值,内存通常在
栈
中分配
引用类型
包含指针、切片slice、map、管道chan、interface等
变量存储的是一个地址,这个地址对应的空间才真正存储数据值,内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收
标识符
标识符命名规则
- 由0-9、a-z、A-Z、_组成,且开头不可以是数字
- golang中严格区分大小写,且标识符中不能包含空格
- 下划线
_
本身在go中是一个特殊的标识符,称为空标识符,可以代表任何其他标识符,但是它对应的值会被忽略(例如,忽略某个返回值时可以使用_),因此,仅能作为占位符使用,无法作为标识符使用 - 不能以系统保留关键字(25个)和预定义标识符(36个)作为自定义标识符,系统保留关键字和预定义标识符如下:
- 系统保留关键字
简化代码编译过程中对代码的解析
- break:强制退出循环或switch语句块。跳出当前循环、switch、select,switchselect默认break,如果不希望break,使用fallthrough,以上这些结构可以用标签命名,break+标签名,可以跳出多层结构
- case:用于switch和select语句块中,后跟一个表达式,表示一个待匹配项
- chan:用于定义channel的关键字,,要用于不同协程直接通信,类似linux管道
- const:常量关键字
- continue:跳过本轮循环,进入下一轮循环迭代。continue也可以使用标签,忽略当前循环结构,直接继续执行标签指定的循环
- default:switch和switch语句块中定义的默认项
- defer:延迟调用关键字。在方法结束(包括异常)时调用,同一个方法内可以定义多个defer,调用顺序和定义相反。注意,defer后跟的表达式是定义时计算的,不是调用时计算的:
func Defer() { i := 1 defer println("first", i+1) i += 1 defer println("second", i) } //输出 //second 2 //first 2
- else:选择判断语句关键字
- fallthrough:switch语句块中使一个case子块执行完毕后,继续执行下一个case子块的代码
- for:循环体语句块关键字
- func: 定义函数和方法的关键字
- go:开启goroutine协程,后面直接跟方法调用
- goto:流程控制关键字,可以讲程序执行流程跳转到一个精确的位置
- if:选择判断语句关键字
- import:导入包,golang目前是不支持循环引用的,例如A import B、B 不能再引用A,A import B、B import C、C import A 也是不可以的
- interface:接口类型
- map:内置map类型,哈希字典
- package:定义包名
- range:定义迭代范围,常用在循环语句块中
- return:结束函数指定,使函数返回
- select: 用于定义通信控制结构的关键字
- struct: 结构体结构关键字
- switch:多重选择判断结构语句块关键字
- 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当然也是可以的
- var:变量定义关键字
- 预定义标识符
包括基础数据类型和系统内嵌函数
- 内建常量: true false iota nil
- 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex64 complex128 bool byte rune string error
- 内建函数: 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开头的十六进制数表示
}
格式化动作分类:
- 通用
%v 值的默认格式表示
%+v 类似%v,但输出结构体时会添加字段名
%#v 值的Go语法表示
%T 值的类型的Go语法表示
%% 百分号
- 布尔值
%t 单词true或false
- 整数
%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)。
- 浮点数与复数
%b 无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
%e 科学计数法,如-1234.456e+78
%E 科学计数法,如-1234.456E+78
%f 有小数部分但无指数部分,如123.456
%F 等价于%f
%g 根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
%G 根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)
- 字符串和字符
%s 直接输出字符串或者[]byte
%q 该值对应的双引号括起来的go语法字符串字面值,必要时会采用安全的转义表示
%x 每个字节用两字符十六进制数表示(使用a-f)
%X 每个字节用两字符十六进制数表示(使用A-F)
- 指针
%p 表示为十六进制,并加上前导的0x
- 其他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放在正负号后面;