1.基础重点
指针
指针是一种直接存储了变量的内存地址的数据类型,也是可见的内存地址,& 操作符可以返回一个变量的内存地址,并且 * 操作符可以获取指针指向的变量内容。
如果用“var x int”声明语句声明一个x变量,那么&x表达式(取x变量的内存地址)将产生一个指向该整数变量的指针,指针对应的数据类型是 *int
,指针被称之为“指向int类型的指针”。
如果指针名字为p,那么*p
表达式对应p指针指向的变量的值,因此它是左值时表示给指针指向的变量赋值,而是右值时表示获取指针指向的变量。
x := 1 p := &x // 类型是 *int 的指针p , 指向 x fmt.Println(*p) // "1" *p = 2 // 等同于 x = 2 fmt.Println(x) // "2" var aPot *int // 初始化指针,此时为指针零值 nil *aPot = 0 // 抛出空指针异常 aPot = new(int) // 使用new开辟新内存 *aPot = 0 // 再取内存地址进行赋值操作
new函数
表达式new(T)将创建一个T类型的匿名变量,初始化为T类型的零值,然后返回变量地址,返回的指针类型为 *T
。
用new创建变量和普通变量声明语句方式创建变量没有什么区别,除了不需要声明一个临时变量的名字外,我们还可以在表达式中使用new(T)。换言之,new函数类似是一种语法糖,而不是一个新的基础概念。
名称定义
如果一个名字是在函数内部定义,那么它就只在函数内部有效。如果是在函数外部定义,那么将在当前包的所有文件中都可以访问。名字的开头字母的大小写决定了名字在包外的可见性。如果一个名字是大写字母开头的,那么它将是导出的,也就是说可以被外部的包访问,例如fmt包的Printf函数就是导出的,可以在fmt包外部访问。包本身的名字一般总是用小写字母。
数组
在数组字面值中,如果在数组的长度位置出现的是“…”省略号,则表示数组的长度是根据初始化值的个数来计算。
q := [...]int{1, 2, 3} fmt.Printf("%T\n", q) // "[3]int"
数组的长度是数组类型的一个组成部分,因此[3]int和[4]int是两种不同的数组类型。数组的长度必须是常量表达式,因为数组的长度需要在编译阶段确定。
Slice(切片)
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;
因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许在函数内部修改底层数组的元素。换句话说,复制一个slice只是对底层的数组创建了一个新的slice别名
和数组不同的是,slice之间不能比较,因此我们不能使用==操作符来判断两个slice是否含有全部相等元素。不过标准库提供了高度优化的bytes.Equal函数来判断两个字节型slice是否相等([]byte),但是对于其他类型的slice,我们必须自己展开每个元素进行比较。
// slice的常见处理方法 // 将slice按特殊符号拼接成字符串 strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)
Map
初始化零值map有两种方式:
a := map[string]int{} b := make(map[string]int)
使用内置的delete函数可以删除元素:
delete(ages, "alice") // remove element ages["alice"]
所有这些操作是安全的,即使这些元素不在map中也没有关系;如果一个查找失败将返回value类型对应的零值。
但是map中的元素并不是一个变量,因此我们不能对map的元素进行取址操作。
结构体
结构体与其成员同样遵循大写导出,小写非导出的命名规范。
结构体变量的成员可以通过点操作符访问:
dilbert.Salary -= 5000 // demoted, for writing too few lines of code
或者是对成员取地址,然后通过指针访问:
position := &dilbert.Position *position = "Senior " + *position // promoted, for outsourcing to Elbonia
点操作符也可以和指向结构体的指针一起工作:
var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)"
相当于下面语句
(*employeeOfTheMonth).Position += " (proactive team player)"
函数的返回值是结构体的指针时,则可以直接通过返回值配合 “.” 操作符来访问结构体的成员,因此它可以通过不断返回一个结构体的指针来达到链式操作的目的。
func EmployeeByID(id int) *Employee { /* ... */ } fmt.Println(EmployeeByID(dilbert.ManagerID).Position) // "Pointy-haired boss" id := dilbert.ID EmployeeByID(id).Salary = 0 // fired for... no real reason
值得注意的是:如果返回值是非指针的结构体,那么则无法直接通过 “.” 操作符来更新返回值的成员,可以通过赋值给新变量后再做操作。
2. time format,快速理解时间格式化
月份 1,01,Jan,January 日 2,02,_2 时 3,03,15,PM,pm,AM,am 分 4,04 秒 5,05 年 06,2006 时区 -07,-0700,Z0700,Z07:00,-07:00,MST 周几 Mon,Monday
您看出规律了么!哦是的,你发现了,这里面没有一个是重复的,所有的值表示都唯一对应一个时间部分。并且涵盖了很多格式组合。
比如小时的表示(原定义是下午3时,也就是15时)
3 用12小时制表示,去掉前导0 03 用12小时制表示,保留前导0 15 用24小时制表示,保留前导0 03pm 用24小时制am/pm表示上下午表示,保留前导0 3pm 用24小时制am/pm表示上下午表示,去掉前导0
又比如月份
1 数字表示月份,去掉前导0 01 数字表示月份,保留前导0 Jan 缩写单词表示月份 January 全单词表示月份
3. 一些问题.
3.1 文件io
//一个二次文件Copy时 第二次文件写入值为0的问题, written,err:=io.Copy(out1, file) fmt.Println(written) //此时为 非0值 (例如:3325) written,err=io.Copy(out2, file) fmt.Println(written) //此时为 0 //原因是第一次copy时file的指针被移至文件末尾. file.Seek(0,0) written,err=io.Copy(out2, file) fmt.Println(written) //此时为 3325 非0值