一、接口介绍和定义

1.1 接口定义了一个对象的行为规范

A. 只定义规范,不实现

B. 具体的对象需要实现规范的细节

葵花宝典:

接口就是一层封装,1个例子,封装一个返还浏览器内容的接口。为什么不直接面向对象呢。你封装成一个接口的话,不论是返回文件或者图片或者html都可以通过接口进行返回,不用接口的话,你需要为每一种返回类型写函数。

1.2 Go中接口定义

A. type 接口名字 interface

B. 接口里面是一组方法签名的集合(后面的调用参数和返回值都要和接口中的方法一模一样)

C.接口是引用类型(指针);注意:函数传递接口类型参数时,一定不要加*,因为接口本身就是引用类型,如果加*就报错了

type Animal interface {
Talk()
Eat() int
Run()
}

1.3 Go中接口的实现

A. 在Go语言中,一个类只要实现了接口要求的所有函数,我们就说这个类实现了该接口

B. 接口类型的变量可以保存实现该接口的任何具体类型的实例。

实例:

package main

import (
"fmt"
) type Animal interface { //定义了动物的规范(接口定义的一组方法)
Eat()
Talk()
Run()
} type Dog struct { //狗如果能够满足了动物的规范(接口方法),那其就是动物
name string
} func (d *Dog) Eat() { //目前狗还不是动物,因为其只实现了Eat,还需要实现Talk和Run,其才算是动物
fmt.Printf("%s is eating\n", d.name)
} func (d *Dog) Talk() {
fmt.Printf("%s is talking\n", d.name)
} func (d *Dog) Run() {
fmt.Printf("%s is runing\n", d.name)
} func (d *Pig) Eat() {
fmt.Printf("%s is eating\n", d.name)
} func (d *Pig) Talk() {
fmt.Printf("%s is talking\n", d.name)
} func (d *Pig) Run() {
fmt.Printf("%s is runing\n", d.name)
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
fmt.Printf("a:%v dog:%v\n", a, dog) //接口底层就是指针,指向的就是一个空对象,如果字节调用就会panic a = dog //dog满足了接口所有方法,所以我们可以直接将其复制给Animal,对应的理论就是接口类型的变量可以保存实现该接口的任何具体类型的实例。
a.Eat()
a.Run()
a.Talk() var pig = &Pig {
name:"佩奇",
} a = pig
a.Eat()
a.Run()
a.Talk()
}

执行结果:

上面这个例子中,Pig和Dog实现了Animal的所有方法,所以Cat和Dog都是动物

小结一下:

Go中的接口只要一个对象实现了接口类型中的所有方法,那么这个对象就实现了这个接口,当然如果一个对象实现了多个interface类型的方法,那么这个对象就实现了多个接口

1.4 接口实例

A. 一个公司需要计算所有职员的薪水

B. 每个职员的薪水计算方式不同

如何解答呢?

我们先单纯通过结构体进行解答:

结构体示例

package main

import (
"fmt"
) type Developer struct { //开发
Name string
Base int
} func (d *Developer) Calc() int {
return d.Base
} type PM struct { //pm
Name string
Base int
Option int
} func (p *PM) Calc() int {
return p.Base + p.Option
} type YY struct { //运营
Name string
Base float32
Option float32
Rate float32 //0.6 ~ 3
} func (p *YY) Calc() float32 {
return p.Base + p.Option*p.Rate
} type EmployeeMgr struct { //最终汇总
devList []*Developer //用切片存
pmList []*PM
yyList []*YY
} func (e *EmployeeMgr) Calc() float32 { //进行计算
var sum float32
for _, v := range e.devList { //计算程序员
sum += float32(v.Calc())
} for _, v := range e.pmList { //计算pm
sum += float32(v.Calc())
} for _, v := range e.yyList { //计算运营
sum += float32(v.Calc())
} return sum
} func (e *EmployeeMgr) AddDev(d *Developer) { //人从哪来,要添加人,添加到列表的函数
e.devList = append(e.devList, d)
} func (e *EmployeeMgr) AddPM(d *PM) {
e.pmList = append(e.pmList, d)
} func (e *EmployeeMgr) AddYY(d *YY) {
e.yyList = append(e.yyList, d)
} func main() {
var e = &EmployeeMgr{} dev := &Developer{ //添加具体人
Name: "develop",
Base: ,
}
e.AddDev(dev) pm := &PM{
Name: "pm",
Base: ,
Option: ,
}
e.AddPM(pm) yy := &YY{
Name: "yy",
Base: ,
Option: ,
Rate: 1.2,
}
e.AddYY(yy) sum := e.Calc() //计算所有人
fmt.Printf("sum:%f\n", sum)
}

执行结果:

解释:

单纯用结构体实现有一个很大的弊端就是如果我们要添加一个职位的话,非常不方便,要改动地方太多,比如:增加职位结构体(类)、结构体方法、计算方式添加、增加进总列表,十分的不灵活,下面我们来看看通过接口实现怎么样

接口实例:

package main

import (
"fmt"
) type Employee interface { //定义1个雇员的接口,其规定的方法是calc(计算工资)
Calc() float32 //接下来的各种职位要想使用这个接口,就需要有该接口规定的方法,并且类型也要一致
} type Developer struct {
Name string
Base float32
} func (d *Developer) Calc() float32 {
return d.Base
} type PM struct {
Name string
Base float32
Option float32
} func (p *PM) Calc() float32 {
return p.Base + p.Option
} type YY struct {
Name string
Base float32
Option float32
Rate float32 //0.6 ~ 3
} func (p *YY) Calc() float32 {
return p.Base + p.Option*p.Rate
} type EmployeeMgr struct {
employeeList []Employee //员工管理列表不需要在区分职位了,只需要1个职员列表即可(因为无论是dev还是pm、yy都实现了employee的接口)
} func (e *EmployeeMgr) Calc() float32 { //计算也是都统一一个了
var sum float32
for _, v := range e.employeeList {
sum += v.Calc()
} return sum
} func (e *EmployeeMgr) AddEmpoyee(d Employee) { //追加也是都只有1个了,并且这里参数也不需要加*,因为interface自身就是引用类型。
e.employeeList = append(e.employeeList, d)
} func main() {
var e = &EmployeeMgr{} dev := &Developer{
Name: "develop",
Base: ,
}
e.AddEmpoyee(dev) pm := &PM{
Name: "pm",
Base: ,
Option: ,
}
e.AddEmpoyee(pm) yy := &YY{
Name: "yy",
Base: ,
Option: ,
Rate: 1.2,
}
e.AddEmpoyee(yy) sum := e.Calc()
fmt.Printf("sum:%f\n", sum)
}

执行结果:

解释:

可以发现用接口来实现,非常方便,新增加一个职位,只需要增加一个职位的类和方法以及该职位职员相关信息即可,十分方便了。

1.5 接口类型变量

A. var a Animal

B. 那么a能够存储所有实现Animal接口的对象实例

实例参考Dog和Pig的实例。

二、空接口

2.1 空接口

A. 空接口没有定义任何方法

B. 所以任何类型都实现了空接口,所以空接口就可以用来存储任何类型的实例。

有一个思考,空接口这么牛逼,可以存储任何类型,那我岂不是可以随意用空接口了?

答:这样不好,首先go语言本身是一个强类型语言,规定传什么类型参数就传什么类型,强类型语言通过规定了,能够提高性能,这也是其的一大特点,而如果不规定,用途就模糊不清了,那样维护性差,可操作性低,就丧失了go的优势了。

interface {

}

实例如下:

package main

import (
"fmt"
) func main() {
var a interface{} //定义1个空接口
var b int a = b //a是空接口,所以可以存储任何类型
fmt.Printf("a=%v a:%T\n", a, a)
var c float32 a = c
fmt.Printf("a=%v a:%T\n", a, a)
}

执行结果:

实例2:

package main

import (
"fmt"
) func describe(i interface{}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
} func main() {
s := "hello world"
describe(s) //空接口可以存string
i :=
describe(i) //空接口可以存int
strt := struct {
name string
}{
name: "Naveen R",
}
describe(strt) //空接口可以存struct,可以证明其可以存储任意类型
}

执行结果:

三、类型断言

3.1 如何获取接口类型里面存储的具体的值呢?

实例1:

package main

import (
"fmt"
) type Animal interface {
Eat()
} type Dog struct {
name string
} func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} type Pig struct {
name string
} func (d *Pig) Eat() {
fmt.Printf("%s is eating\n", d.name)
} //类型断言
func Describe(a Animal) { //注意Animal(接口)本身就是引用类型,这里不能加*,加上会报错
dog := a.(*Dog) //将animal指定为dog(但是animal中不一定只有dog)
dog.Eat()
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
a = dog
fmt.Printf("I am dog\n")
Describe(a) //dog调用类型断言的describe函数 var pig = &Pig{
name: "佩奇",
} a = pig
fmt.Printf("I am pig\n")
Describe(a) //pig也调用类型断言的describe函数
}

执行结果:

解释:

可以发现直接报错了,这是因为我们强行将animal定为dog,但是animal中还有pig,所以pig在调用describe函数时,就冲突了,就报错了。这是一个很大的坑,如何解决呢?

解决办法:

引入 ok判断机制!

实例如下:

package main

import (
"fmt"
) type Animal interface {
Eat()
} type Dog struct {
name string
} func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} type Pig struct {
name string
} func (d *Pig) Eat() {
fmt.Printf("%s is eating\n", d.name)
} //类型断言
func Describe(a Animal) { //注意Animal(接口)本身就是引用类型,这里不能加*,加上会报错
dog, ok := a.(*Dog) //引入ok判断机制
if !ok {
fmt.Printf("convert to dog failed\n")
return
}
fmt.Printf("describe suncc\n")
dog.Eat()
fmt.Printf("describe suncc----------\n")
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
a = dog
fmt.Printf("I am dog\n")
Describe(a) var pig = &Pig{
name: "佩奇",
} a = pig
fmt.Printf("I am pig\n")
Describe(a)
}

执行结果:

解释:

我们可以发现引入ok判断机制后,将animal定为dog,但是animal中还有pig,当确定dog时就会执行成功,而是pig则就会失败。

实例2:

package main

import (
"fmt"
) func assert(i interface{}) {
s := i.(int) //将空接口传入的类型定为int
fmt.Println(s)
} func main() {
var s interface{} = "harden"
assert(s)
}

执行结果:

解决上述报错:

引入ok机制:

package main

import (
"fmt"
) func assert(i interface{}) {
v, ok := i.(int) //将空接口传入的类型定为int
fmt.Println(v, ok)
} func main() {
var s interface{} =
assert(s)
var i interface{} = "harden"
assert(i)
}

执行结果:

3.2 type switch

方法1:问题:需要转2次

实例1:

package main

import (
"fmt"
) type Animal interface {
Eat()
} type Dog struct {
name string
} func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} type Pig struct {
name string
} func (d *Pig) Eat() {
fmt.Printf("%s is eating\n", d.name)
} //types switch
func DescribeSwitch(a Animal) {
fmt.Printf("DescribeSwitch(a) begin\n")
switch a.(type) { //格式是固定的,type是一个关键字 //强制转第一次type关键字
case *Dog: //强制转换成dog(强制转第二次)
dog := a.(*Dog)
dog.Eat()
case *Pig: //强制转成pig(强制转第二次)
pig := a.(*Pig)
pig.Eat()
}
fmt.Printf("DescribeSwitch(a) end\n")
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
a = dog
fmt.Printf("I am dog\n")
DescribeSwitch(a) var pig = &Pig{
name: "佩奇",
} a = pig
fmt.Printf("I am pig\n")
DescribeSwitch(a)
}

执行结果:

实例2:

package main

import (
"fmt"
) func findType(i interface{}) {
switch i.(type) {
case string:
fmt.Printf("I am a string and my value is %s\n", i.(string))
case int:
fmt.Printf("I am a int and my value is %d\n", i.(int))
default:
fmt.Printf("Unknown type\n")
}
} func main() {
findType("hello")
findType()
findType(88.98)
}

执行结果:

方法2:(推荐) 解决需要转2次问题

实例1:

package main

import (
"fmt"
) type Animal interface {
Eat()
} type Dog struct {
name string
} func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} type Pig struct {
name string
} func (d *Pig) Eat() {
fmt.Printf("%s is eating\n", d.name)
} //types switch
func DescribeSwitch(a Animal) {
fmt.Printf("DescribeSwitch(a) begin\n")
switch v := a.(type) { //格式是固定的,type是一个关键字
case *Dog:
v.Eat() //v就是断言之后的具体类型
case *Pig:
v.Eat()
}
fmt.Printf("DescribeSwitch(a) end\n")
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
a = dog
fmt.Printf("I am dog\n")
DescribeSwitch(a) var pig = &Pig{
name: "佩奇",
} a = pig
fmt.Printf("I am pig\n")
DescribeSwitch(a)
}

执行结果:

实例2:

package main

import (
"fmt"
) func findType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Printf("I am a string and my value is %s\n", v)
case int:
fmt.Printf("I am a int and my value is %d\n", v)
default:
fmt.Printf("Unknown type\n")
}
} func main() {
findType("hello")
findType()
findType(88.98)
}

执行结果:

四、指针接收和值接收

葵花宝典:

指针类型实现的接口,值类型是赋值不了的。但是值类型实现的接口,指针类型依然可以赋值

例子如下:

比如说Dog是通过指针类型实现的接口:

当我们进行值类型赋值就会报错:

我们可以通过传入地址来解决:

但如果Dog是值类型实现的接口:

我们的指针类型对其进行赋值依然没问题:

这是因为go在传入时帮我们将指针类型转换为了值类型。

五、多接口

5.1 多接口

同一个类型可以实现多个接口。

比如说狗是一个动物,它可以实现动物的接口,狗同时还是一个哺乳动物,他也可以实现哺乳动物的接口。重要的是:只要把多个接口的方法都实现了,那么其就可以实现多个接口。

package main

import (
"fmt"
) //定义2个接口
type Animal interface {
Eat()
} type BuRuAnimal interface {
ChiNai()
} type Dog struct {
name string
} //Dog实现了上述2个接口的方法,所以其也实现了上述2个接口
func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} func (d *Dog) ChiNai() {
fmt.Printf("%s is ChiNai\n", d.name)
} func main() {
var dog = &Dog{
name: "旺财",
} var a Animal
fmt.Printf("a:%v dog:%v\n", a, dog)
a = dog
a.Eat() var b BuRuAnimal
b = dog
b.ChiNai()
}

执行结果:

5.2 接口嵌套

package main

import (
"fmt"
) //定义2个接口
type Animal interface {
Eat()
} type BuRuAnimal interface {
ChiNai()
} //接口嵌套
type AdvanceAnimal interface { //要想实现AdvanceAnimal接口,那么就需要满足嵌套的接口的所有方法
Animal
BuRuAnimal
} type Dog struct {
name string
} func (d *Dog) Eat() {
fmt.Printf("%s is eating\n", d.name)
} func (d *Dog) ChiNai() {
fmt.Printf("%s is ChiNai\n", d.name)
} func main() {
var dog = &Dog{
name: "旺财",
} var a AdvanceAnimal
fmt.Printf("a:%v dog:%v\n", a, dog)
a = dog
a.Eat()
a.ChiNai()
}

执行结果:

六、接口实例讲解

6.1 io包中的writer接口

底层writer接口的规定的方法:

实例:

package main

import (
"fmt"
"os"
) type Test struct {
data string
} //这里实现1个wirter接口的方法
func (t *Test) Write(p []byte) (n int, err error) {
t.data = string(p)
return len(p), nil
} func main() {
file, _ := os.Create("c:/tmp/c.txt")
fmt.Fprintf(os.Stdout, "hello world\n") //输出到终端 (这里FPrintf函数要传入一个writer类型接口,把具体实例os.Stdout传给writer接口)
fmt.Fprintf(file, "hello world\n")
/* 因为writer接口的存在,我们可以省去下面的步骤:
fmt.FPtrintfConsole()
fmt.FPtrintfFile()
fmt.FPtrintfNet()
*/
var t *Test = &Test{}
fmt.Fprintf(t, "this is a test inteface:%s", "?akdfkdfjdkfk\n") //存入到data中了 fmt.Printf("t.data:%s\n", t.data)
}

执行结果:

6.2 fmt包中的Stringer接口

底层stringer接口

实例:

package main

import (
"encoding/json"
"fmt"
) type Student struct {
Name string
Age int
} func (s *Student) String() string {
data, _ := json.Marshal(s)
return string(data)
} func main() {
var a = &Student{
Name: "hell",
Age: ,
}
fmt.Printf("a = %v\n", a) //fmt包调Print相关函数时,看传进去的变量是否实现了stringer接口
}

执行结果:

解释:

Printf函数会判断传入的变量a是否实现了stringer接口,stringer接口会调其中的string方法去格式化输出字符串

6.3 error包中的error接口

底层error接口:

type error interface {
Error() string
}

实例1:

package main

import (
"fmt"
"time"
) type MyError struct {
When time.Time
What string
} func (e MyError) Error() string {
str := fmt.Sprintf("at %v, %s", e.When, e.What)
fmt.Printf("1:%T\n", str)
return str
}
func run() error {
fmt.Println("")
str := MyError{time.Now(), "it didn't work"}
fmt.Printf("2:%T\n", str)
fmt.Println(MyError{time.Now(), "it didn't work"})
return str
}
func main() {
if err := run(); err != nil {
fmt.Printf("3:%T\n", err)
fmt.Println(err)
}
}

执行结果:

解释:

因为error接口只有1个Error方法,所以我们就可以实现自己的错误类型,此处我们就是定义了myerror结构体,但是我们实现了error方法,在函数中返回时返回error,就把我们自己的错误类型反回给了error接口了

实例2:

package main

import (
"fmt"
) type ErrNegativeSqrt float64 func (e ErrNegativeSqrt) Error() string { return fmt.Sprintf("cannot Sqrt negative number:%v", float64(e))
} func Sqrt(x float64) (float64, error) {
if x < {
return , ErrNegativeSqrt(x) //如果小于0,则返回我们自定义的错误
} return x, nil
} func main() {
fmt.Println(Sqrt())
_, err := Sqrt(-)
if err != nil {
switch err.(type) { //类型断言,获取错误码
case ErrNegativeSqrt:
fmt.Printf("ErrNegativeSqrt\n")
default: }
}
}

执行结果:

6.4 Reader接口

实例:

package main

import (
"fmt"
_ "strings"
"time"
) type MyReader struct{} // TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte) (int, error) {
b[] = 'A'
return , nil
}
func main() {
var myre MyReader
b := make([]byte, )
//for {
//r := strings.NewReader(b)
myre.Read(b)
fmt.Printf("%c\n", b[])
time.Sleep( * time.Second)
myre.Read(b)
fmt.Println(b[])
//}
}

执行结果:

解释:

定义1个Myreader结构体,下面实现reader方法,实例实现了reader方法,按照定义的read方法去操作。

6.5 Image接口

image接口底层:

type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}

实例:

package main

import (
"image"
"image/color" "golang.org/x/tour/pic"
//"fmt"
) type Image struct {
weight int
height int
} func (c Image) ColorModel() color.Model {
return color.RGBAModel
}
func (b *Image) Bounds() image.Rectangle {
return image.Rect(, , b.weight, b.height)
}
func (a *Image) At(x, y int) color.Color {
//fmt.Println(x, y)
return color.RGBA{uint8(x), uint8(y), , }
}
func main() {
m := &Image{, }
//m.At(225, 0)
pic.ShowImage(m) //m.At(x, y)的参数由pic传入,传入了所有情况
}

解释:

定义了image的结构体,下面实现了底层image接口的方法,所以我们就可以传入实例进行操作。

Go语言基础之18--接口编程的更多相关文章

  1. Python语言基础07-面向对象编程基础

    本文收录在Python从入门到精通系列文章系列 1. 了解面对对象编程 活在当下的程序员应该都听过"面向对象编程"一词,也经常有人问能不能用一句话解释下什么是"面向对象编 ...

  2. PL/SQL语言基础

    PL/SQL语言基础 进行PL/SQL编程前,要打开输出set serveroutput on 1.创建一个匿名PL/SQL块,将下列字符输出到屏幕:"My PL/SQL Block Wor ...

  3. Go语言基础之接口(面向对象编程下)

    1 接口 1.1 接口介绍 接口(interface)是Go语言中核心部分,Go语言提供面向接口编程,那么接口是什么? 现实生活中,有许多接口的例子,比如说电子设备上的充电接口,这个充电接口能干什么, ...

  4. Go语言面组合式向对象编程基础总结

    转自:http://blog.csdn.net/yue7603835/article/details/44282823 Go语言的面向对象编程简单而干净,通过非侵入式接口模型,否定了C/C++ Jav ...

  5. GO学习-(19) Go语言基础之网络编程

    Go语言基础之网络编程 现在我们几乎每天都在使用互联网,我们前面已经学习了如何编写Go语言程序,但是如何才能让我们的程序通过网络互相通信呢?本章我们就一起来学习下Go语言中的网络编程. 关于网络编程其 ...

  6. GO学习-(14) Go语言基础之接口

    Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口类型 在Go语言中接口(interface)是一种类型,一种抽象的类 ...

  7. Go语言基础之接口

    Go语言基础之接口 接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节. 接口 接口介绍 在Go语言中接口(interface)是一种类型,一种抽象的类 ...

  8. C语言与MATLAB接口 编程与实例 李传军编着

    罗列一下以前自己学习C语言与MATLAB混编的笔记,顺便复习一遍. <C语言与MATLAB接口 编程与实例 李传军编着>(未看完,目前看到P106) 目录P4-8 ************ ...

  9. 2018.3.5 Java语言基础与面向对象编程实践

    Java语言基础与面向对象编程实践 第一章 初识Java 1.Java特点 http://www.manew.com/blog-166576-20164.html Java语言面向对象的 Java语言 ...

随机推荐

  1. git pull没有指定branch的报错

    执行git pull或者git push的时,有时候会出现如下报错: $ git pull You asked me to pull without telling me which branch y ...

  2. [codeforces126B]Password

    解题关键:KMP算法中NEXT数组的理解. #include<bits/stdc++.h> #define maxn 1000006 using namespace std; typede ...

  3. global作用域

    1   global在函数内部 $somevar=15; function addit () { GLOBAL $somevar; $somevar++ ; echo "somevar is ...

  4. Spring集成MyBatis01 【推荐使用】、springMVC中文乱码和json转换问题

    1 导包 1.1 spring-webmvc : spring框架包(当然里面也包含springmvc) 1.2 mybatis : mybatis框架包 1.3 mybatis-spring : s ...

  5. Excel VBA 若要在64位系统上使用,则必须更新此项目中的代码,请检查并更新Declare语句,然后用PtrSafe属性标记它们

    在Office 2010 32位上开发的Excel VBA系统,迁移到Office 2010 64位下面,打开后使用,报下面错误: 解决办法:  在Declare 后面加PtrSafe 进行标记

  6. p2114 起床困难综合症

    传送门 分析 orz zwj 最好想到的方法是我们枚举每一位是0还是1,然后暴力求出经过n个操作之后的结果来决定这一位是0还是1 然后我们发现这种暴力的做法居然能a 但是还有更好的方法 我们只考虑开始 ...

  7. Spring学习大纲

    1.BeanFactory 和 FactoryBean? 2.Spring IOC 的理解,其初始化过程? 3.BeanFactory 和 ApplicationContext? 4.Spring B ...

  8. [译]Javascript中的递归函数

    本文翻译youtube上的up主kudvenkat的javascript tutorial播放单 源地址在此: https://www.youtube.com/watch?v=PMsVM7rjupU& ...

  9. Sass和Compass设计师指南 Ben Frain 中文高清PDF扫描版​

    Sass和Compass设计师指南是<响应式Web设计:HTML5和CSS3实战>作者Ben Frain的又一力作.作者通过丰富.完整的案例,循序渐进地展示了Sass和Compass的使用 ...

  10. HTML与CSS入门经典(第9版)试读 附随书源码 pdf扫描版​

    HTML与CSS入门经典(第9版)是经典畅销图书<HTML与CSS入门经典>的最新版本,与过去的版本相同,本书采用直观.循序渐进的方法,为读者讲解使用HTML5与CSS3设计.创建并维护世 ...