在去年的时候,Louis Lazaris在sitepoint上发了一篇文章 [http

参考:
12 Little-Known CSS Facts
12 Little-Known CSS Facts (The Sequel)

CSS 本身并不是非常复杂的语言,但是即使你写过很多的CSS样式,你总会感觉到对它并不了解,使用了很多年之后你还是会偶遇很多你没有用过的“有意思”的样式属性,本文作者列举了一些这样的Tricks。

color属性不只是作用域文本

color属性大家都很熟悉,一般用来设置文本(Text)的颜色属性,但是如果你把body标签的color设置为某种颜色,比如color: yellow几乎所有的东西就都会变成黄色的,包括图片的替代文本,列表的bullet,border等,但是hr标签并不会默认继承这个颜色,需要手动设置 border-color: inherit才会变成黄色。
color属性的spec中是这么定义的:

This property describes the foreground color of an element’s text
content. In addition it is used to provide a potential indirect value
… for any other properties that accept color values.

visibility属性可以设置为”collapse”

所有元素的visibility属性的默认值都是visible, 我们可以把它设置为hidden来隐藏一个元素,与dispaly: none表现不同的是,hidden的元素仍然会占据原来的空间(still occupy space)
visibility属性还有一个值就是collapse,它的表现一般和hidden是一样的,但是在table中表现会不一样,可能你已经想到了,在table元素中的行,行组,列,列组中使用collapse属性它的表现和display:none是一样的。
不过用于浏览器对这个属性的表现并不一致,所以还是不要使用

background属性有了新的内容

在CSS 2.1中,background属性有color, image, repeat, attachment, position 5个“子内容”,在CSS3 中,添加很了一些新内容:

background: [background-color] [background-image] [background-repeat]
[background-attachment] [background-position] / [ background-size]
[background-origin] [background-clip]

一个示例:

.example {
background: aquamarine url(img.png)
no-repeat
scroll
center center / 50%
content-box content-box;
}

这些新属性在所有现代浏览器都能正常工作,只不过好像用处也不大。

clip属性只在绝对定位元素上起作用

clip属性可以用来对元素进行裁剪,它的值是一个图形,比如rect,或者是auto。
但是要注意,clip属性只对决定定位元素起作用,包括position: absolute;,和position: fixed两种情况。
使用:

.example {
position: absolute;
clip: rect(110px, 160px, 170px, 60px;
}

它的表现可以参考这张图片:
更多关于clip的内容,可以查看这里

垂直百分比和容器的宽度有关而不是高度

这个听起来有点奇怪,我们知道百分比计算宽度是基于容器的宽度的,但是实际上padding-top, margin-top, padding-bottom, margin-bottom这些属性的百分比属性也是基于容器的宽度的,而不是高度。
这是挺有意思的。

border属性 is Kind of Like Inception

border属性我们会经常使用,比如这样子:

.somecontainer {
border: solid 1px black;
}

我们知道border属性是border-style, border=width, border-color三个属性的综合声明。但是其实这三个属性都可以对上下左右四条边分别设置属性:

.somecontainer {
border-width: 2px 5px 1px 0;
}

这样可以把border设置的很复杂,不过在实际使用中,并不要这么用,因为这样使得整个元素显得混乱而导致页面风格混乱。

text-decoration 现在也成为了一种速记符(shorthand)

新的css规范中,可以这样使用text-decoration属性:

a {
text-decoration: overline aqua wavy;
}

第一个值是text-decoration-line, 第二个是text-decoration-color, 第三个是 text-dcoration-style属性
不过这个属性只在firefox中实现了。

border-width属性可以接受关键字值

现在border-width 可以接受 medium, thin, thick关键字了。他们在浏览器实现中大概是1px, 2px, 5px;不过在spec中并没有指定他们具体的值。

几乎没人使用botder-image

border-image看起来是个不错的属性,使某些表现变得整洁,有一个特别经典的例子是在一圈文字的border加一圈花or狗爪印?不过在现实中几乎没有人会使用这个属性,我也没有使用过。。

empty-cells属性

这个属性被所有主流浏览器支持,包括IE8。它的用法如下:

table {
empty-cell: hide;
}

这个属性用于table元素中空td的样式。或许会有用哦。 测试: codepen

font-style属性接受“oblique”值

这个看起来和 font-style: italic 并没有什么区别,都是斜体字体,spec中是这么描述的:

“…selects a font that is labeled as an oblique face, or an italic face if one is not.”

如果设置的字体没有真正的italic样式(face),那么font-style:italicfont-style: oblique的表现是一样的。

word-wrap 属性和overflow-wrap属性是一样的

word-wrap是微软提出的一个属性,所以在IE老版本中都有很好的支持,但是W3C决定使用overflow-wrap 替换掉 word-wrap。但是现在所有的浏览器都支持 word-wrap属性了,所以要替换也不是那么容易的事了。

没什么问题的话还是继续使用word-wrap吧。

待续

不定参数

不定参数的用法已经知道了,函数可以定义如下:

func sum (nums ...int){
total := 0
for _, num := range nums {
total += num
}
return total
}

这样可以计算任意个传入的int参数的和,但是有下面的用法:

nums := []int{1,2,3,4,5}
t := sum(nums...)

nums... 这样的用法在之前没有遇到过,它可以用来表示, 将一个slice的元素作为函数的实参列表。

闭包

对于使用过js的人,闭包是一个非常熟悉的概念了。js使用闭包实现内部变量的私有化,go中也有同样的目的?
与js不同的是在定义函数的地方要把返回值写明返回的函数的签名。

func intSeq() func() int {
i := 0
return func() int {
i += 1;
return i
}
}

这样定义的intSeq函数调用后的返回值就是一个函数。

next := intSeq()
// next是一个命名函数了
next()

相比js的闭包,功能不是那么丰富,不过本质差不多。

指针

Go提供了指针支持,允许传递值的引用。函数的参数可以是某种类型的指针。比如:

func zeroptr(i *int) {
*i = 0;
}

i := 1
zeroptr(&i)

对于指针要理解星号(*)符号和引用符号(&)的作用。

Assigning a value to a dereferenced pointer changes the value at the referenced address.

语法 &i得到的是i的内存地址,也就是i的指针。也可能存在指向指针的指针。在函数体内部,*i将指针/内存地址 解引用(dereferendes)为地址指向的值。所以赋值语句为*i=0这中形式。

结构体

go中使用关键字struct定义结构体,和C语言的结构体非常相似,不过相比C语言要简单很多了,没有C语言那些复杂的指针维护。
结构体可以使用字面量初始化:

// 定义一个结构
type Person struct {
name string
age int
}

me := Person{"wuxu", 22}
anotherMe := Person{name:"wuxu", age:22}
mePtr := &me

还是要注意指针的理解, me是一个对象, &me返回的是对象的地址,复制给 mePtr, 如果要将mePtr的内存存放新的对象应该如下:

*mePtr = Person{"new Name", 33}
// 错误: mePtr = Person{"new name", 33}

修改了mePtr的对象,me的对象也被修改了,因为他们是同一片内存区域。

方法(method)

我们知道在Go中可以给struct“绑定”方法,其实现方式如下:

func (p Person) say(words string) {
fmt.Println(p.name, "says: ", words)
}

func (p *Person) run(d int) {
fmt.Println(p.name, " runs away ", d, " meters")
}

上面给Person结构体绑定了两个方法,看以看到,方法绑定中,一个使用 Person ,一个是 *Person他们有什么区别呢?
在方法调用时,Go会自动处理变量和指针的转换,即下面的调用时正确的:

me := Person("wuxu", 22)
me.say("wtf")
me.run(100)

虽然run方法的参数类型是 *Person,使用me调用也能正确执行,那么在绑定参数时的*有什么作用? 这里其实和值与引用相似。不带*的参数相当于值传递,带*的相当于引用传递,所以在say方法中对p的属性修改不会改变调用者的值,而在run方法中修改p的属性会表现在调用者上。

Go automatically handles conversion between values and pointers for method calls. You may want to use a pointer receiver type to avoid copying on method calls or to allow the method to mutate the receiving struct.

但是要注意,如果使用 *的话,方法便不再是绑定在Person结构体上了,而是在*Person上了。
虽然Person的对象也能调用,这在接口实现中会有表现: 如果一个接口定义了run方法,那么实现这个接口的struct绑定方法时,不能使用*

接口

Go的接口,只要定义的类型“绑定”了接口中定义的方法,就认为类型实现了接口。没有implements这类的显式声明实现接口。

一个接口实现的示例:

type animal interface {
run(int)
}

type Pig struct {
name string
age int
}
func (p Pig) run(d int) {
fmt.Println("Pig runs away ", d)
}

func runAway(an animal) {
an.run(100);
}
func inters() {
runAway(Person{"TM", 22, false})
runAway(Pig{"wangcai", 1})
}

关于接口的更多内容可以查看 这篇博客

错误

我们知道Go没有异常系统,而是通过一个显式声明的独立返回值(利用多返回值特性)来声明错误。一般在最后一个返回值声明错误。
error是系统提供的返回错误接口,可以自己定制类型实现该接口作为定制的错误类型返回。如果错误位返回nil表示没有错误。

func (e myErr) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func (p Person)buy(item string) (bool, error) {
if p.age < 18 {
return false, &myErr{0, "too yong"}
}
return true, nil
}

常见的自定义错误如上,注意几点,在返回处, 需要使用 &myErr,否则会报类型错误。如果没有自定义错误,可以直接使用内置的错误类型

return false, errors.New("too yong")

注意,需要导入errors包。
使用自定义错误的好处是可以在调用出对错误做类型判断,已进行相应的处理。

_, e := me.buy("shirt")
if me, ok := e.(*myErr); ok {
// with myErr return, me ref to return error
...
}

待续

见第二部分

github上面的活跃的dotfiles项目[yadr](https

这里主要就是官方readme文档的 Vim部分 的记录。基本的vim操作就不详细介绍了。

参考: http://skwp.github.io/dotfiles/

有什么?

yadr提供了

导航

  • ,z 切换到前一个缓冲文件(buffer),相当于 :bp[revious]
  • ,x 切换到后一个缓冲,相当于命令行的 :bn[ext]
  • Alt+jAlt+k 下跳或者上跳一个函数;好像不起作用
  • Ctrl+o 跳转到旧的指针位置,这是vim的标准快捷键,非常有用
  • Ctrl+i Ctrl+o的逆操作,也是标准按键

搜索与代码导航

  • ,f 跳转到类定义,需要系统安装了ctags,并创建tags file。

ctags是一个独立的软件,不包含在vim之中,使用yum安装即可,在项目目录下运行 ctags -R . 后会创建tags file,这是就可以使用跳转到定义的功能了。

  • ,F,f相同不过在新建垂直分屏(vsp)显示定义
  • ,gf或者 ctrl+f 跳转到光标坐在变量名对应的文件,但是在一个新的分栏中显示,这在java中比较好用
  • gF 标准快捷键,打开文件
  • K 搜索光标所在单词,并在quickfix窗口显示结果,这个功能需要安装silver serach, sudo yum install -y the_silver_searcher
  • ,K 没太多用,Grep the current word up to next exclamation point (useful for ruby foo! methods)
  • ,hl 开关搜索结果的高亮,相当于 :set noh与其逆操作
  • ,gg或者,ag: 搜索,键入这个命令后,会出现一个输入符,在双引号中输入字符后回车会搜索包含这段字符的行
  • ,gd 搜索包含字符的函数定义, grep def,不太用
  • ,gcf grep current file的缩写,查找对本文件的引用
  • // 清空搜索
  • ,,w ,<Esc>的alias,EasyMotion,highlights jump-points on the screen and lets you type to get there
  • ,mc 多标签,这个功能和sublime中的比较像,在一个单词上面使用,mc会记录这个单词,然后使用Ctrl-n或者Ctrl+p选择前一个或者后一个相同的单词的,或者使用Ctrl+x跳过一个。 这个比较有用
  • gK 打开光标所在单词同名的文档?不常用

文件导航

  • ,t 文件选择器,不常- 用,键入指令后可以输入字符筛选文件
  • ,b 打开缓存区的一个文件,比较常用
  • Cmd-Shift-M 跳转到方法,Linux下好像没用。
  • ,jm jump to models,可能在rails里面比较有用
  • Alt+Shift+N nerd tree toogle,好像没什么用
  • Ctrl+\ 在侧边栏文件树显示当前文件,和sublime的sidebar有点像
  • Cmd-Shift-P 清空ctrlp 缓存,没用过

常用编辑快捷键

  • Ctrl+space 自动补全- , Tab键
  • ,#; ,"; ,'; ,]; ,); ,}; 这些优点麻烦的样子,待补充
  • ,. 跳转到上一个编辑地点。和'.的作用相同。
  • ,ci change inside any set of quotes/brackets/etc。其作用是删除”, {}, ()之间的字符并进入插入模式

多标签,多窗口,分割模式

  • alt+n - 快速跳转到指定的tab
  • Ctrl-h,j,k,l 在分栏窗口中上下左右移动。
  • Q 关闭当前的window,非常方便
  • vvCtrl-w,v一样,vertical split, 上下分栏
  • ssCtrl-w,s一样,horizontal split,左右分栏
  • ,qo 打开quickfix, qo => quickfix open, 有用
  • ,qc 关闭quickfix, qc => quickfix close, 有用,yadr配置的vim在保存文件时会做语法检查,如果检查不通过会在quickfix窗口显示错误,并且不会自动消失,这时候也许需要 ,qc来关闭它。

其它常用

  • Ctrl-p 循环历史剪切板,这个很有用,p是粘贴,Ctrl+p会粘贴之前的剪切板内容
  • ,yr view yanking, 查看历史复制记录, q退出查看
  • crs, crc, cru cr 应该是 coerce(强制), s是snake, c是camelcase, u是UPPER,转换变量大小写下划线形式,比较有意思,可以使用:help abolish查看更多
  • :NR NarrowRgn,看名字不知道是干什么的,其实是选中一段代码,然后:NRvim会新开一个split把选中的代码放在心的split中操作,操作完后,wq会把结果覆盖掉原来选中的代码。也挺有用的。
  • ,ig toggle visual indentation guids
  • ,cf 拷贝当前文件的完整路径到系统剪切板
  • ,cn 拷贝文件名
  • ,yw yw是从复制从光标位置开始到单词结束,,yw是在任意位置复制整个单词
  • ,ow 使用yank buffer中内容覆盖当前所在的单词,ow => overwrite
  • ,ocf 打开所有git标记为修改过的文件,在splits中打开。使用git版本控制下的文件才有用, ocf => open changed files
  • ,w 去掉尾部多余的空格,应该很有用, StripTrailingWhitespaces
  • sj 把单行的hash表格式化为多行的
  • sk unsplit a link 应该不怎么用
  • ,he he => html escape
  • ,hu hu => html unescape
  • Alt+Shift-A align things, 好像没什么用
  • :ColorToggle 顾名思义
  • :Gitv git log browser
  • ,hi 显示当前高亮的组
  • ,gt Go Tidy, 格式化html代码
  • :Wrap 折叠长行,这个比较有用,特别是quickfix中的提示经常跑出去了 // 好像对quickfix部分不起作用 -_-
  • Cmd-/ toggle comments,在Linux下使用alt
  • gcp comment a paragraph

vim相关

,vr 重新加载vim, vim reload

Go语言提供了一套“新颖的”错误系统,与Java中的异常系统不一样,Go中没有抛出异常的概念

基础知识

如果有一定面向对象语言编程经验的话,肯定会对异常/错误系统有一定的了解。如果使用Java比较多的话,一定会认为抛异常是编程语言天生的一部分,因为在Java中抛异常真的是太家常便饭,无处不在了;但是在Go语言中,根本没有异常这个东西!

看一段官方的QA是怎么解释这个问题的:

We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.

Go takes a different approach. For plain error handling, Go’s multi-value returns make it easy to report an error without overloading the return value. A canonical error type, coupled with Go’s other features, makes error handling pleasant but quite different from that in other languages.

Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function’s state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.

多值返回为Go提供了一个新的错误处理机制,如果对为什么Go不提供异常体系感兴趣可以在 这里 找到更多的细节。

在之前文章中,已经使用过Go语言的多值返回来返回详细的错误描述。按照规定,错误的类型通常为error,这是一个内建的简单接口

type error interface {
Error() string
}

可以通过实现这个接口定制错误,例如打开文件时的错误:

type PathError struct {
Op string
Path string
Err error // 由系统调用返回
}

fanc (pe *PathError) Error() string {
// 返回定制的错误信息
return pe.Op + " " + pe.Path + ": " + pe.Err.Error()
}

effective go中有一个示例非常有利于我们理解错误的使用,这段代码的作用是创建一个文件,如果创建失败,检查是不是磁盘空间不足,如果是则删除掉一些临时文件,再尝试创建一次:

for try:=0; try<2; try++ {
file, err := os.Create(filename)
if err == nill {
return //没有错误
}
// 检查是不是空间不足
if e,ok := err.(*os.PathError); ok && e.Err == syscall.ENOSPC {
deleteTempFiles() // 删除一些临时文件
continue // 再创建一次
}
return
}

可以查看源码中PathError的实现 https://golang.org/src/os/error.go

Panic函数

函数可以通过将error作为额外的返回值来向调用者报告错误,对于一些“致命”的错误,他们导致程序不能继续运行了,Go内建了一个Panic函数,它会产生一个运行时错误并重孩子程序。该函数接受一个任意类型的实参在程序终止时打印。

只需要在需要它的地方调用一下panix()就可以了。

func init() {
if user == "" {
panic("user变量必须有值")
}
}

这是一个示例,但是如果是设计库函数则应该避免使用panic,一个常见的使用panic的场景是初始化,如上面的示例。

恢复

panic函数被调用后,程序将终止当前函数的执行。并开始追溯Go程的栈,运行任何被推迟的函数。我如果回溯到栈顶,程序会终止。
我们可以通过使用内建的recover函数来重新取回Go程的控制权。
调用recover将停止回溯,并返回传入panic的是西安。因为回溯只有被推迟执行的函数中的代码在执行,所以recover也只能在被推迟的函数中才有效。

一个应用:在服务器中终止失败的Go程而无需杀死其他正在执行的Go程:

func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work)
}
}

func safelyDo(work *Work) {
// defer 函数是被推迟执行的
defer func() {
if err := recover(); err != nil {
log.Println("work failed:", err)
}
}()
do(work)
}

如果do函数中调用panic函数,回溯会执行defer的函数,这时recover会起作用,打印错误信息后退出Go程。
可以使用上面的思路简化错误处理。

A标签默认是inline元素,所以标签内容的长短决定了鼠标hover的范围,在有些时候,比如某些导航列表中,需要hover父标签(li)时能hover到a标签。

在制作一个API文档的侧边栏导航列表的时候,html大概是这样的

<div class="toc-macro absolute">
<ul class="toc-indentation">
<li><a href="#id-1">1. 绑定手机号</a></li>
<li><a href="#id-2">2. 查询手机号码是否可以绑定</a></li>
<li><a href="#id-3">3. 添加邮箱</a></li>
<li><a href="#id-4">4. 检查邮箱是否可以添加</a></li>
</ul>
</div>

其他的样式就不具体写了,其实现的效果是在右侧边栏实现一个列表导航。一些主要的样式大概如下:

.toc-macro{ position: fixed; top: 100px; }
.toc-macro ul {list-style-type:none; }
.toc-macro a { text-decoration: none; }
.toc-macro li { padding: 6px 8px 2px;text-decoration:none; }
li:hover { background-color: #efefef;color: #7DB75C; }
.toc-macro a:hover { background-color: #efefef;color: #7DB75C; }

其中使用了决定定位时导航不会随着滚动条而滚动,使我们在查看API文档时总是能看到这个导航。
这样子基本符合我们的需求了,但是有一个小小的问题,那就是鼠标放到导航上如果没有放到a链接上面,li标签会变色,但是由于鼠标没有在a上所以还是箭头的形状。。。这个小小的瑕疵需要解决一下。

首先我们想到的是把a标签的宽度设置到 100%:

.toc-macro a { display:inline-block; width: 100%; }

但是这样似乎并没有按照我们所想的表现出来。我们需要再加一些东西,让a标签的inline-block可以把宽度填充

.toc-macro li { vertical-align: middle; line-height: 15px;}

另外还有一种实现思路那就是把div下所有标签的display属性设置为block,同时a的height设置为100%:

.toc-macro ul { display: block; }
.toc-macro li { display: block;}
.toc-macro a { display:block; height:100%; }

这样实现应该也是可行的,不过没有测试过了。

一些vim插件需要提供Lua支持,特别是常见的补全插件,前段时间安装的[yadr](https

下载源码

首先,编译安装嘛,先下载源码, 可是呢这两sourceforge挂掉了,vim是托管在sf上的,导致下载页面也不能访问了,甚至vim的官网 www.vim.org 也不能访问了,幸好vim在github上有一个备份 https://github.com/vim/vim 或者直接访问vim.org的ftp站: ftp://ftp.vim.org/pub/vim/Unix/

// 使用下面之一的方法下载源码
// git下载的话体积会大一些,好处是以后可以方便地更新
git clone git@github.com:vim/vim.git
wget -O ftp://ftp.vim.org/pub/vim/Unix/vim-7.4.tar.bz2
wget ftp://ftp.vim.org/pub/vim/Unix/vim-7.4.tar.bz2

tar xjvf vim-7.4.tar.bz2
cd vim74

编译

vim的编译其实很简单,就configure->make->make install 这样的流程。但是要添加 Lua支持,就有一些麻烦了。

configure的配置大概是这样的:

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes

首先如果不在 configure配置那手动打开 --enable-fail-if-missing 这个选项,你会发现,configure没有问题,make没有问题,make install也OK,但是运行生成的vim: vim --version会发现Lua前面还是一个”-“(表示没有Lua支持)
因为其实configure那根本就没有找到lua的支持,只是默认跳过了 --enable-luainterp=yes 选项。。。
所以应该这样运行configure:

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes --enable-fail-if-missing

如果你的机器没有安装lua 和luajit的话会在检查lua支持那里中断了。

安装Lua和LuaJit

Lua的安装倒是比较方便,官方repo里面就有,当然要想安装新版的也可以自己编译安装。

sudo yum install lua lua-devel -y

luajit不在centos的官方repo里面,我们需要编译安装;

wget http://luajit.org/download/LuaJIT-2.0.4.tar.gz
tar -xzvf LuaJIT-2.0.4.tar.gz
cd LuaJIT-2.0.4
// 使用默认安装路径
make
sudo make install

编译安装

安装好lua支持后,再运行configure就不会报错了

./configure --prefix=/data/vim74 --with-features=huge --with-luajit --enable-luainterp=yes --enable-fail-if-missing
make
sudo make install

运行

运行编译的vim:

/data/vim74/bin/vim

可能你会发现这样的错误:

/data/vim74/bin/vim: error while loading shared libraries: libluajit-5.1.so.2: cannot open shared object file: No such file or directory

显然是安装的luajit有问题。我们找一下luajit这个.so文件在哪里

sudo find / -name libluajit-5.1.so.2

发现下面有这个文件:

  1. /home/wuxu/tars/ngx_openresty-1.7.10.2/build/luajit-root/data/openresty/luajit/lib/libluajit-5.1.so.2
  2. /usr/local/lib/libluajit-5.1.so.2
  3. /data/openresty/luajit/lib/libluajit-5.1.so.2

显然1,3是openresty编译的,第二个才是我们安装luajit支持,我们需要给这个.so生成一个软链接,让vim能找到它:

sudo ln -s /usr/local/lib/libluajit-5.1.so.2 /lib64/libluajit-5.1.so.2

这样再运行vim就不会有问题了~也有Lua支持了,不过Vim的自动补全还没用过呢,学会了再做记录吧。

effective go 中的部分笔记,这是第三部分

部分笔记摘要,参考: https://go-zh.org/doc/effective_go.html

空白标识符

空白标识符是指”_”,是一个特殊的变量标识,在之前的for…range 循环中已经使用过。

为未使用的变量或者包使用

在Go中如果定义了一个变量没有使用,或者导入一个没有使用的包会在编译时报错,未使用的包会让程序体积变大并拖慢编译速度,初始化了而不是用的变量会留下某种隐患。
但是某些时候我们确实需要丢弃一些没有的变量,比如在for range循环中,可能需要丢弃键或者值,或者程序写到中途某些变量已经定义只是还没有使用等等,这时就需要空白标识符了。

// 检查某个路径是否存在
if _, err := os.Stat(paht); os.IsNotExist(err) {
fmt.Println("path not exist")
}

对于未使用的变量和未使用的导入包,可以把它们先赋值给空白标识符”_”关闭编译器的错误,在使用了这些变量后再删掉这些代码:

fd = 32
....
_ = fd
var _ = fmt.Printf // 用于调试使用,结束时删除

接口检查

如果只需要判断某个类型是否实现了某个接口而不需要实际使用接口本身,可以使用空白标识符:

if _, ok := val.(Milk); ok {
fmt.Printf("value %v has implement interface milk", val)
}

// 接口检查示例
type iMilk interface {
getName() string
}
type Milk struct {
name string
}
func (m Milk) getName() string {
return m.name
}
func main() {
var m interface {}
m = Milk{"yili"}
if _,ok := m.(iMilk); ok {
fmt.Printf("value %v has implement iMilk\n ", m)
} else {
fmt.Println("has not implememt")
}
}

内嵌/Embedding

Go语言并不提供典型的类与继承系统,所以也没有子类化的概念。继承是一个重要的概念,但是很多时候更推荐的做法是使用组合,Go提供的内嵌和组合概念差不多,可以将类型内嵌到结构体或者接口中,实现某种组合功能。

一种典型的内嵌的ReadWriter接口的定义:

type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
// 一个结合了Reader和Writer的接口
type ReadWriter interface {
Reader
Writer
}

// 也可以内嵌到一个结构体中
type ReadWriter struct {
r *Reader
w *Writer
}

内嵌的接口一般需要实现被内嵌接口的方法,满足其需求,比如要提供Read方法:

func (re *ReadWriter) Read(p []byte) (n int, err error) {
return rw.r.Read(p)
}

而通过直接内嵌结构体,我们就能避免如此繁琐。 内嵌类型的方法可以直接引用
当内嵌一个类型时,该类型的方法会成为外部类型的方法, 但当它们被调用时,该方法的接收者是内部类型,而非外部的。在我们的例子中,当 bufio.ReadWriter 的 Read 方法被调用时, 它与之前写的转发方法具有同样的效果;接收者是 ReadWriter 的 reader 字段,而非 ReadWriter 本身:

type Logger struct {
content string
}
func (l Logger) Log(s string) {
l.content += s
fmt.Println(s)
}
type infoLog struct {
count int
*Logger
}
func main() {
var logger = infoLog{0, &Logger{"sss"} }
// 可以直接调用内嵌类型的方法
logger.Log("embedding function invoke")
}

内嵌类型可能会引起命名冲突的问题。更深层次的命名字段或方法会被覆盖。若相同的嵌套层级上出现同名冲突,通常会产生一个错误。但是如果冲突的名字不会被使用就没有问题……

并发部分有点多,单独做一篇吧

在php中经常会使用memcache或者memcached来做缓存系统,为了方便操作经常会对PHP提供的接口进行一层封装

todo

effective go 中的部分笔记,这是第四部分

部分笔记摘要,参考: https://go-zh.org/doc/effective_go.html

并发

通过通信共享内存

并发编程中很麻烦的一点是对共享变量访问的控制,在一般环境中,使用同步互斥锁机制等实现互斥的访问,使得代码繁琐而且难以理解。Go语言提出了一个独特的机制:信道。
Go将共享的值通过信道传递。在给定的时间点,只有一个Go程能访问信道中的值,在设计上杜绝了数据在同一时间点被放线程访问。

不要通过共享内存来通信,而应通过通信来共享内存。

为了方便理解,可以把Go的并发处理方式看做类型安全的Unix管道的实现。

Go程/Goroutines

Go程是Golang提出的术语,Go程有简单的模型,它是与其他Go程并发运行在同一地址空间的函数。Go程可以看作是轻量级的线程,它的开销几乎只有栈空间的分配,它的使用很廉价。
Go程在多线程操作系统上可以实现多路复用,后面会详细讲到。多路复用在多线程编程中非常重要。

那如何运行一个Go程呢?很简单,只需要在要调用的函数前添加”go”关键字就能在Go程中运行这个函数,函数调用结束时,Go程也就自动退出。

go list.Sort()
// 使用函数字面量运行
go func() {
// do something here
}

信道/Channels

前面已经提到了信道的概念,在多线程中,具体怎么使用呢,下面就详细介绍信道(channel)

之前讲过,在Go中切片,映射和信道都使用make来分配内存,make返回的值充当对底层数据结构的引用。信道的声明和初始化示例如下:

// 前两个是无缓冲的正数类型信道
c1 := make(chan int)
c2 := make(chan int, 0)
// 指向文件指针的带缓冲区的信道
c3 := make(chan *os.File, 100)

若不指定第二个参数,信道就是无缓冲的同步信道。使用一个同步信道,一个示例如下:

c1 := make(chan int) // 一个不带缓冲的,同步信道
go func() {
time.Sleep(500 * time.Millisecond)
// send one value to channl
c1 <- 1
}()
// do something here
fmt.Println("wait for goroutine to quir")
<-c1 // 主线程在此等待Go程结束,丢弃信道中的值,因为该值只是用作同步的标识
fmt.Println("go routine done")

看完无缓冲的信道,下面看一下有缓冲的信道。有缓冲的信道可以看作是信号量,操作系统里面学过信号量的东西。信号量可以看作是资源不足时用来限制线程执行,使其等待的机制。常用来限制吞吐量,限制调用的process的数量等。
比如要对Web请求使用Go程处理:

func Serve(queue chan *Request) {
for req := range queue {
sem <- 1
// 考虑到闭包的性质,这里传入值 *Request,
// 如果不使用 *Request则所有Go程处理的是同一个变量req
go func(req *Request) {
process(req)
<-sem
}(req)
}

// 另一种方式是,重新声明一个变量
for req := range queue {
// 参考js中for循环中使用timeInterval函数时要重新声明变量
req := req // 为该Go程创建 req 的新实例。
sem <- 1
go func() {
process(req)
<-sem
}()
}
}

信道中的信道/channels of channels

这部分比较麻烦,待续

并行化/Parallelization

在多CPU核心上实现并行计算。

并发是用可独立执行的组件构造程序的方法, 而并行则是为了效率在多CPU上平行地进行计算。Go仍然是种并发而非并行的语言,且Go的模型并不适合所有的并行问题

目前Go运行时的实现默认并不会并行执行代码,它只为用户层代码提供单一的处理核心。 任意数量的Go程都可能在系统调用中被阻塞,而在任意时刻默认只有一个会执行用户层代码。 它应当变得更智能,而且它将来肯定会变得更智能。但现在,若你希望CPU并行执行, 就必须告诉运行时你希望同时有多少Go程能执行代码。有两种途径可以使用:

  1. 在运行你的工作时将 GOMAXPROCS 环境变量设为你要使用的核心数
  2. 导入 runtime 包并调用 runtime.GOMAXPROCS(NCPU)。 runtime.NumCPU() 的值可能很有用,它会返回当前机器的逻辑CPU核心数。

当然,随着调度算法和运行时的改进,将来会不再需要这种方法。

一个缓冲区泄漏示例/A leak buffer

客户端Go程从某些来源,可能是网络中循环接收数据。为避免分配和释放缓冲区, 它保存了一个空闲链表,使用一个带缓冲信道表示。若信道为空,就会分配新的缓冲区。 一旦消息缓冲区就绪,它将通过 serverChan 被发送到服务器。 serverChan.

var freeList = make(chan *Buffer, 100)
var serverChan = make(chan *Buffer)

func client() {
for {
var b *Buffer
// 若缓冲区可用就用它,不可用就分配个新的。
select {
case b = <-freeList:
// 获取一个,不做别的。
default:
// 非空闲,因此分配一个新的。
b = new(Buffer)
}
load(b) // 从网络中读取下一条消息。
serverChan <- b // 发送至服务器。
}
}

服务器从客户端循环接收每个消息,处理它们,并将缓冲区返回给空闲列表。

func server() {
for {
b := <-serverChan // 等待工作。
process(b)
// 若缓冲区有空间就重用它。
select {
case freeList <- b:
// 将缓冲区放大空闲列表中,不做别的。
default:
// 空闲列表已满,保持就好。
}
}
}

客户端试图从 freeList 中获取缓冲区;若没有缓冲区可用, 它就将分配一个新的。服务器将 b 放回空闲列表 freeList 中直到列表已满,此时缓冲区将被丢弃,并被垃圾回收器回收。(select 语句中的 default 子句在没有条件符合时执行,这也就意味着 selects 永远不会被阻塞。)依靠带缓冲的信道和垃圾回收器的记录, 我们仅用短短几行代码就构建了一个可能导致缓冲区槽位泄露的空闲列表。(没懂TT)

上一段的英文如下,总觉得中文的翻译不太对

The client attempts to retrieve a buffer from freeList; if none is available, it allocates a fresh one. The server’s send to freeList puts b back on the free list unless the list is full, in which case the buffer is dropped on the floor to be reclaimed by the garbage collector. (The default clauses in the select statements execute when no other case is ready, meaning that the selects never block.) This implementation builds a leaky bucket free list in just a few lines, relying on the buffered channel and the garbage collector for bookkeeping.

对于频繁的字符串操作,可以使用bytes.Buffer接口提供的方法快速拼接

和一般语言类似,Go也为string重载了”+”操作符,可以使用它进行字符串拼接,但是在C#和Java中对于大量的字符串拼接操作推荐使用StringBuilder这样的类,同样对于Go中的大量字符串拼接也不推荐直接使用”+”操作。

可以使用bytes.Buffer提供的方法进行快速拼接:

import (
"bytes"
"fmt"
)
func concat() string {
var buffer bytes.Buffer

for i:=0; i<1000; i++ {
buffer.WriteString("abcd")
}
str := buffer.String()
// do something to str
...
return str
}

这样可以获得O(n)的时间复杂度。

参考: stackoverflow.com/