new

new(type)返回的是类型type对象的指针

和直接通过var声明变量一样,new会根据不同的类型,赋予相应的零值(未初始化)

例如new([]int)得到的是一个nil切片的头指针

make

make函数只能用于创建切片、map和通道channel变量,并且会对这些变量进行初始化,不再是零值,可以直接进行使用,例如make 生成的切片可以直接赋值使用。

make返回的是变量而不是指针,由于make的变量都是些数据结构,所以必须要初始化,保证后续代码对这些变量的正确使用。

new和make的区别

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

打印函数

Go的标准库fmt中包含了许多用于打印的函数

例如Print,Println,Sprintf,Fprintf等等,其中Sprintf返回的是一个字符串,而不是像C++那样将格式化后的字符串写入缓冲区中。

下面的几种打印方式,结果是一样的。

fmt.Printf("Hello %d\n", 23)
fmt.Fprint(os.Stdout, "Hello ", 23, "\n")
fmt.Println("Hello", 23)
fmt.Println(fmt.Sprint("Hello ", 23))

注意到Fprintf的第一个参数是一个实现了io.Writer接口的类型。

格式化字符:

**格式 ** 含义
%% 一个%字面量
%b 一个二进制整数值(基数为2),或者是一个(高级的)用科学计数法表示的指数为2的浮点数
%c 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符
%d 一个十进制数值(基数为10)
%e 以科学记数法e表示的浮点数或者复数值
%E 以科学记数法E表示的浮点数或者复数值
%f 以标准记数法表示的浮点数或者复数值
%g 以%e或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%G 以%E或者%f表示的浮点数或者复数,任何一个都以最为紧凑的方式输出
%o 一个以八进制表示的数字(基数为8)
%p 以十六进制(基数为16)表示的一个值的地址,前缀为0x,字母使用小写的a-f表示
%q 使用Go语法以及必须时使用转义,以双引号括起来的字符串或者字节切片[]byte,或者是以单引号括起来的数字
%s 字符串。输出字符串中的字符直至字符串中的空字符(字符串以'\0‘结尾,这个'\0'即空字符)
%t 以true或者false输出的布尔值
%T 使用Go语法输出的值的类型
%U 一个用Unicode表示法表示的整型码点,默认值为4个数字字符
%v 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的String()方式输出的自定义值,如果该方法存在的话
%x 以十六进制表示的整型值(基数为十六),数字a-f使用小写表示
%X 以十六进制表示的整型值(基数为十六),数字A-F使用小写表示
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF

请注意fmt的两个使用技巧。通常Printf格式化字符串包含多个%参数时将会包含对应相同数量的额外操作数,但是%之后的[1]副词告诉Printf函数再次使用第一个操作数。第二,%后的#副词告诉Printf在用%o、%x或%X输出时生成0、0x或0X前缀。

当打印一个结构体时,使用%+v同时打印结构体的字段名和该字段对应的值,使用%#v则能够打印出Go中完整的符号:

m2:= map[int]string{2:"world"}
fmt.Printf("%+v\n", m2)
fmt.Printf("%#v\n", m2)
//output
map[2:world]
map[int]string{2:"world"}

如果想通过%v按照自己想要的输出内容打印自己定义的类型,可以为自己的类型添加String() string 方法,fmt包打印时会调用这个函数。

type newStruct struct {
	a, b int
}

func (ns *newStruct) String() string {
	return fmt.Sprintf("In newStruct a is %d b is %d", ns.a, ns.b)
}

func main(){
	ns := new(newStruct)
	ns.a = 123
	ns.b = 456
	fmt.Printf("%v\n", ns)
}
//output
In newStruct a is 123 b is 456

如果说想通过%v既可以自定义打印对象指针,也可以打印对象,那么String方法的接收者必须为该类型的值类型,而不是指针类型:

type newStruct struct {
	a, b int
}

func (ns newStruct) String() string {
	return fmt.Sprintf("In newStruct a is %d b is %d", ns.a, ns.b)
}
func main(){
	ns := new(newStruct)
	ns.a = 123
	ns.b = 456
	nns := newStruct{a: 777, b: 888}
	fmt.Printf("%v\n", ns)
	fmt.Printf("%v\n", nns)
}
//output
In newStruct a is 123 b is 456
In newStruct a is 777 b is 888

Sprintf函数以%s字符串的形式打印一个类型对象时,也会去调用该类型的实现的String方法,因为它想要一个字符串结果,所以,要注意不能在实现类型A的String函数的内部调用Sprintf去打印A,因为会一直递归调用String。如果真要在String函数内部打印类型A对象本身,一个简单的方法就是强制转换成没有实现String函数的类型,例如基本的string类型就没有实现该方法:

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

fmt包的打印函数都可以接收多个参数,打印多个变量的内容,因为空接口类型可以接收任何类型的对象,所以这些函数原型大致如下:

func Printf(format string, v ...interface{})

Within the function Printf, v acts like a variable of type []interface{} but if it is passed to another variadic function, it acts like a regular list of arguments.

其中...代表这个变量其实是一个包,作为参数时可以理解成v是一个包,而在函数内部使用时,可以看成是一个列表:

// Println prints to the standard logger in the manner of fmt.Println.
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))  // Output takes parameters (int, string)
}

在这个参数的函数内部,这个变量如同一个切片一样,而如果把他传给另外一个支持可变参数的函数,那么这个就可以看成是一个参数列表,所以上面的Sprintln中的v后缀必须要加...,不然传给这个函数的v是一个值,而不是一个列表(包)。

其实对于这种...的参数,可以看成是一个切片:

func listParams(a ...int) {
	for _, val := range a {
		fmt.Println(val)
	}

}
func main(){
	listParams(1,2,3)
}
//output
1
2
3