var表示变量, type表示类型
type person struct {name string
age int
}
var P person // P现在就是person类型的变量了
P.name = "Astaxie" // 赋值"Astaxie"给P的name属性.
P.age = 25 // 赋值"25"给变量P的age属性
fmt.Printf("The person's name is %s", P.name) // 访问P的name属性
除了上面这种P的声明使用之外,还有两种声明使用方式
1.按照顺序提供初始化值
P := person{"Tom", 25}
2.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
array
array就是数组,它的定义方式如下:var arr [n]type
数组可以使用另一种:=来声明
a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组
b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
由于长度也是数组类型的一部分,因此[3]int与[4]int是不同的类型,数组也就不能改变长度。数组之间的赋值是
值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。如果要使用指
针,那么就需要用到后面介绍的slice类型了。
slice
slice并不是真正意义上的动态数组,而是一个引用类型。slice总是指向一个底层array,slice的声明也可以像
array一样,只是不需要长度
slice(切片)是引用类型,所以当引用改变其中元素的值时,其它的所有引用都会改变该值
append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。
map
定义方法:
1. // 先声明map
var m1 map[string]string
// 再使用make函数创建一个非nil的map,nil map不能赋值
m1 = make(map[string]string)
// 最后给已声明的map赋值
m1["a"] = "aa"
m1["b"] = "bb"
2. // 直接创建
m2 := make(map[string]string)
// 然后赋值
m2["a"] = "aa"
m2["b"] = "bb"
3. // 初始化 + 赋值一体化
m3 := map[string]string{
"a": "aa",
"b": "bb",
}
使用:
// ==========================================
// 查找键值是否存在
if v, ok := m1["a"]; ok {
fmt.Println(v)
} else {
fmt.Println("Key Not Found")
}
// 遍历map
for k, v := range m1 {
fmt.Println(k, v)
}
map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变
m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut" // 现在m["hello"]的值已经是Salut了
make、new操作
make用于内建类型(map、slice 和channel)的内存分配。new用于各种类型的内存分
传值与传针
当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。
传指针有什么好处呢?
1 传指针使得多个函数能操作同一个对象。
2 传指针比较轻量级 (8bytes),只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的
话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用
指针是一个明智的选择。
3 Go语言中string,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递
指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
import
一些特殊的import,让很多新手很费解,下面我们来一一讲解一下
到底是怎么一回事1. 点操作
我们有时候会看到如下的方式导入包
import(
. "fmt"
)
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调
用的fmt.Println("hello world")可以省略的写成Println("hello world")
2. 别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import(
f "fmt"
)
别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")
3. _操作
这个操作经常是让很多人费解的一个操作符,请看下面这个import
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
interface变量存储的类型
我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:
Comma-ok断言
goroutine
runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。
默认情况下,调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显示的调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。以后Go的新版本中调度得到改进后,这将被移除。
channel
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
runtime goroutine
runtime包中有几个处理goroutine的函数:
Goexit
退出当前执行的goroutine,但是defer函数还会继续调用
Gosched让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。NumCPU
返回 CPU 核数量
NumGoroutine
返回正在执 行和排队的任务总数
GOMAXPROCS
用来设置可以运行的CPU核数
与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每
个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。
cookie是有时间限制的,根据生命期不同分成两种:会话cookie和持久cookie;
如果不设置过期时间,则表示这个cookie生命周期为从创建到浏览器关闭止,只要关闭浏览器窗口,cookie就消失
了。这种生命期为浏览会话期的cookie被称为会话cookie。会话cookie一般不保存在硬盘上而是保存在内存里。
如果设置了过期时间(setMaxAge(606024)),浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些
cookie依然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗
口。而对于保存在内存的cookie,不同的浏览器有不同的处理方式。
Go设置cookie
Go语言中通过net/http包中的SetCookie来设置:
http.SetCookie(w ResponseWriter, cookie *Cookie)