go 指南--接口方法篇(接口以及方法的运用)

方法篇

go指南地址:https://tour.go-zh.org/methods/1

1.方法

Go 没有类。然而,仍然可以在结构体类型上定义方法。

方法接收者 出现在 func 关键字和方法名之间的参数中。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Println(v.Abs())
}

输出:
5

2.方法续

你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。

但是,不能对来自其他包的类型或基础类型定义方法

package main

import (
    "fmt"
    "math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt2)    //求二次根
    fmt.Println(f.Abs()) 
}

输出:
1.4142135623730951

3.接收者为指针的方法

方法可以与命名类型或命名类型的指针关联。

刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。

尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。

当 v 是 Vertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。

Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := &Vertex{3, 4}
    fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
    v.Scale(5)  //结构体的值被Scale方法修改了,这里类的实现者Vertex传入的是指针; 如果是值拷贝,结构体内数据不会发生变化
    fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}

输出:
Before scaling: &{X:3 Y:4}, Abs: 5
After scaling: &{X:15 Y:20}, Abs: 25

接口篇

接口

接口类型是由一组方法定义的集合。

接口类型的值可以存放实现这些方法的任何值。

注意: 示例代码的 22 行存在一个错误。 由于 Abs 只定义在 *Vertex(指针类型)上, 所以 Vertex(值类型)不满足 Abser。

package main

import (
    "fmt"
    "math"
)

type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f  // a MyFloat 实现了 Abser
    a = &v // a *Vertex 实现了 Abser

    // 下面一行,v 是一个 Vertex(而不是 *Vertex)
    // 所以没有实现 Abser。
    //a = v

    fmt.Println(MyFloat(-math.Sqrt2).Abs()) //若方法不是通过调用指针实现的,可直接初始化类并且调用方法,结构体同上:fmt.Println(Vertex{3, 4}.Abs()) (如果Vertex不是通过指针调用的)
    fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {    //这里的方法的实现类的类型是指针,所以调用该方法时,也只能通过指针调用:v := Vertex{3, 4}; var a Abser = &v; fmt.Println(a.Abs())
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

输出:
1.4142135623730951
5

隐式接口

类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

包 io 定义了 Reader 和 Writer;其实不一定要这么做。

package main

import (
    "fmt"
    "os"
)

type Reader interface {
    Read(b []byte) (n int, err error)
}

type Writer interface {
    Write(b []byte) (n int, err error)
}

type ReadWriter interface {
    Reader
    Writer
}

func main() {
    var w Writer

    // os.Stdout 实现了 Writer
    w = os.Stdout

    fmt.Fprintf(w, "hello, writer\n")
}

输出:
hello, writer

Stringers(内建接口,包含String() string 方法)

一个普遍存在的接口是 fmt 包中定义的 Stringer。

type Stringer interface {
String() string
}
Stringer 是一个可以用字符串描述自己的类型。fmt包 (还有许多其他包)使用这个来进行输出。

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"Arthur Dent", 42}
fmt.Printf("Person 类型: %T\n", a)
    z := Person{"Zaphod Beeblebrox", 9001}
    fmt.Println(a,"|", z)
}

输出:
Person 类型: main.Person
Arthur Dent (42 years) | Zaphod Beeblebrox (9001 years)

练习:Stringers

让 IPAddr 类型实现 fmt.Stringer 以便用点分格式输出地址。

例如,IPAddr{1, 2, 3, 4} 应当输出 “1.2.3.4”。

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string{
    return fmt.Sprintf("%v,%v.%v.%v", ip[0], ip[1], ip[2], ip[3])    //Sprintf:格式化返回数据
}

func main() {
    addrs := map[string]IPAddr{
        "loopback":  {127, 0, 0, 1},
        "googleDNS": {8, 8, 8, 8},
    }
    for n, a := range addrs {
        fmt.Printf("%v: %v\n", n, a)
    }
}

输出:
loopback: 127,0.0.1
googleDNS: 8,8.8.8

接口方法篇

错误 (error接口实现)

Go 程序使用 error 值来表示错误状态。

与 fmt.Stringer 类似, error 类型是一个内建接口:

type error interface {
Error() string
}
(与 fmt.Stringer 类似,fmt 包在输出时也会试图匹配 error。)

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil, 来进行错误处理。

i, err := strconv.Atoi(“42”)
if err != nil {
fmt.Printf(“couldn’t convert number: %v\n”, err)
return
}
fmt.Println(“Converted integer:”, i)
error 为 nil 时表示成功;非 nil 的 error 表示错误。

package main

import (
    "fmt"
    "time"
    "errors"
    "strconv"
)

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("0")
    str := MyError{
        time.Now(),
        "it didn't work",
    }
fmt.Printf("2:%T\n", str)
//fmt.Println(str.Error())
fmt.Println(MyError{
        time.Now(),
        "it didn't work",
    })
    return str
}

func test() error{
    return errors.New("test err")
}


func main() {
    if err := run(); err != nil {
fmt.Printf("3:%T\n", err)
        fmt.Println(err)
    }
//以下为测试其他类实现的Error()接口,这里为*strconv.NumError类型
    errtest := test()
    if errtest != nil{
fmt.Printf("4:%T\n", errtest)
        fmt.Println(errtest)
    }

    i, errtest2 := strconv.Atoi("as")   //构造错误信息
if errtest2 != nil {
fmt.Printf("5:%T\n", errtest2)
    fmt.Printf("couldn't convert number: %v\n", errtest2)   //代码执行此处
    return
}
fmt.Println("Converted integer:", i)

}

输出:
0
2:main.MyError
1:string
at 2009-11-10 23:00:00 +0000 UTC m=+0.000000000, it didn’t work
3:main.MyError
1:string
at 2009-11-10 23:00:00 +0000 UTC m=+0.000000000, it didn’t work
4:*errors.errorString
test err
5:*strconv.NumError
couldn’t convert number: strconv.Atoi: parsing “as”: invalid syntax

练习:错误

从先前的练习中复制 Sqrt 函数,并修改使其返回 error 值。

由于不支持复数,当 Sqrt 接收到一个负数时,应当返回一个非 nil 的错误值。

创建一个新类型

type ErrNegativeSqrt float64
为其实现

func (e ErrNegativeSqrt) Error() string
使其成为一个 error, 该方法就可以让 ErrNegativeSqrt(-2).Error() 返回 "cannot Sqrt negative number: -2"

注意: 在 Error 方法内调用 fmt.Sprint(e) 将会让程序陷入死循环。可以通过先转换 e 来避免这个问题:fmt.Sprint(float64(e))。请思考这是为什么呢?

修改 Sqrt 函数,使其接受一个负数时,返回 ErrNegativeSqrt 值。

package main

import (
    "fmt"
    "math"
)
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 < 0 {
        return 0, ErrNegativeSqrt(x)
    }
    //牛顿法求二次根
    z := float64(1)
     for {
          y := z - (z*z-x)/(2*z)
          if math.Abs(y-z) < 1e-10 {
               return y, nil
          }
          z = y
     }
     return z, nil
}

func main() {
    fmt.Println(Sqrt(2))
    fmt.Println(Sqrt(-2))
}

输出:
1.4142135623730951
0 cannot Sqrt negative number:-2

Readers (io.reader调用系统实现)

io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。

Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)
Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。

例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

package main

import (
    "fmt"
    "io"
    "strings"
)

func main() {
    r := strings.NewReader("Hello, Reader!")

    b := make([]byte, 8)
    for {
        n, err := r.Read(b)    //b为字节切片,是通过指针传递的
        fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
        fmt.Printf("b[:n] = %q\n", b[:n])
        if err == io.EOF {
            break
        }
    }
}

输出:
n = 8 err = b = [72 101 108 108 111 44 32 82]
b[:n] = “Hello, R”
n = 6 err = b = [101 97 100 101 114 33 32 82]
b[:n] = “eader!”
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = “”

练习:Reader(实现read()方法)

实现一个 Reader 类型,它不断生成 ASCII 字符 ‘A’ 的流。

package main

import ( 
    "golang.org/x/tour/reader"
    "time"
    "fmt"
    _"strings"
)

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte) (int, error){
    b[0] = 'A'
    return 1, nil
}

func main() {
    reader.Validate(MyReader{})    //

    var myre MyReader
    b := make([]byte, 1)
    //for{
        //r := strings.NewReader(b)
        myre.Read(b)
        fmt.Printf("%c\n", b[0])
        time.Sleep(1 *time.Second)
        myre.Read(b)
        fmt.Println(b[0])
    //}

}

输出:
OK!
A
65

练习:rot13Reader (通过类修改流) [???]

一个常见模式是 io.Reader 包裹另一个 io.Reader,然后通过某种形式修改数据流。

例如,gzip.NewReader 函数接受 io.Reader(压缩的数据流)并且返回同样实现了 io.Reader 的 *gzip.Reader(解压缩后的数据流)。

编写一个实现了 io.Reader 的 rot13Reader, 并从一个 io.Reader 读取, 利用 rot13 代换密码对数据流进行修改。

已经帮你构造了 rot13Reader 类型。 通过实现 Read 方法使其匹配 io.Reader。

package main

import (
    "io"
    "os"
    "strings"
    "errors"
    "fmt"
)

type rot13Reader struct {
    r io.Reader
}
//需要实现io.Reader 类型的方法:Read([]byte) (int, error)
func (rot rot13Reader) Read(buf []byte) (int, error){
fmt.Println(1)

    l, err := rot.r.Read(buf)    //???
    if err!= nil{
        return 0, errors.New("some wrong")
    }

    //rot.r.Read(buf)
    for k, v := range buf{
        if v == byte(0){
            return k, nil
        }
        buf[k] = v+'a'
    }

    return l, nil
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    //fmt.Printf("%v\n", r)
    io.Copy(os.Stdout, r)

    buf := make([]byte, 30)
    **_, err := r.Read(buf)**   //切片是以指针形式赋值的???
    fmt.Println(err, buf)

}

输出:
1
��Ɂ������ҁ��Ӂ���ӂ1 //这是修改后的流
1
some wrong [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] //测试[]byte

图片 (image接口)

Package image 定义了 Image 接口:

package image

type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
注意:Bounds 方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image 包中。

(参阅文档了解全部信息。)

color.Color 和 color.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由image/color 包定义。

package main

import (
    "fmt"
    "image"
)

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    fmt.Println(m.Bounds())
    fmt.Println(m.At(0, 0).RGBA())
}

输出:
(0,0)-(100,100)
0 0 0 0

练习:图片 (输出 颜色块)

还记得之前编写的图片生成器吗?现在来另外编写一个,不过这次将会返回 image.Image 来代替 slice 的数据。

自定义的 Image 类型,要实现必要的方法,并且调用 pic.ShowImage。

Bounds 应当返回一个 image.Rectangle,例如 image.Rect(0, 0, w, h)

ColorModel 应当返回 color.RGBAModel。

At 应当返回一个颜色;在这个例子里,在最后一个图片生成器的值 v 匹配 color.RGBA{v, v, 255, 255}

package main

import (
    "golang.org/x/tour/pic"
    "image"
    "image/color"
    //"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(0, 0, b.weight, b.height)
}

func (a *Image) At(x, y int) color.Color{
    //fmt.Println(x, y)
    return color.RGBA{uint8(x), uint8(y), 255, 255}
}

func main() {
    m := &Image{700,50}
    //m.At(225, 0)
    pic.ShowImage(m) //m.At(x, y)的参数由pic传入,传入了所有情况
}

输出:这里写图片描述

http篇

Web 服务器

包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:

package http

type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
在这个例子中,类型 Hello 实现了 http.Handler。

访问 http://localhost:4000/ 会看到来自程序的问候。

注意: 这个例子无法在基于 web 的指南用户界面运行。为了尝试编写 web 服务器,可能需要安装 Go。

package main

import (
    "fmt"
    "log"
    "net/http"
)

type Hello struct{}

func (h Hello) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request) {
    fmt.Fprint(w, "Hello!")
}

func main() {
    var h Hello
    err := http.ListenAndServe("localhost:4000", h)
    if err != nil {
        log.Fatal(err)
    }
}

执行curl localhost:4000
输出:Hello!

练习:HTTP 处理

实现下面的类型,并在其上定义 ServeHTTP 方法。在 web 服务器中注册它们来处理指定的路径。

type String string

type Struct struct {
Greeting string
Punct string
Who string
}
例如,可以使用如下方式注册处理方法:

http.Handle(“/string”, String(“I’m a frayed knot.”))
http.Handle(“/struct”, &Struct{“Hello”, “:”, “Gophers!”})
在启动你的 http 服务器后,你将能够访问: http://localhost:4000/stringhttp://localhost:4000/struct.

注意: 这个例子无法在基于 web 的用户界面下运行。 为了尝试编写 web 服务,你可能需要 安装 go

package main

import (
    "log"
    "net/http"
    "fmt"
)

type String string

type Struct struct{
    greet string
    comma string
    who string
}

func (str String) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request){

    fmt.Fprint(w, str)  
}

func (stuc Struct) ServeHTTP(
    w http.ResponseWriter,
    r *http.Request){

    fmt.Fprint(w, stuc.greet, stuc.comma, stuc.who)
}

func main() {
    //赋值类结构体返回的是对象,可直接调用对象实现的接口的方法
    //string1 := String("I'm a frayed knot.")
    //struct1 := &Struct{"Hello", ":", "Gophers!"}
    //http.Handle("/string", string1)
    //http.Handle("/struct", struct1)

    //可直接给类结构体赋值顺便执行方法,可省去再返回利用实体对象再调用方法
    http.Handle("/string", String("I'm a frayed knot."))
    http.Handle("/struct", &Struct{"Hello", ":", "Gophers!"})
    // your http.Handle calls here
    log.Fatal(http.ListenAndServe("localhost:4000", nil)) //必须在绑定处理路径之后

}

curl localhost:4000/string
输出:I’m a frayed knot.
curl localhost:4000/struct
输出:Hello”, “:”, “Gophers!

已标记关键词 清除标记
相关推荐
本书是讲述Delphi最经典的著作之一,作为Delphi的新版本,Delphi 6不仅能帮助程序员高效开发Windows应用程序,简化Web服务、中间软件以及后台数据库系统的合成,还是目前惟一全面支持所有主流业界标准的开发工具和提高电子商务能力的利器。本书内容丰富、条理清晰,用深入浅出的语言阐述了Delphi 6的精髓,对Delphi或即将移植到Delphi的程序员来说,具有极大的参考价值。本书最后还介绍了Delphi在Internet方面的应用。在移动商务网络编程越来越热的今天,无疑会成为程序员关注的焦点 译者序\r\n序言\r\n前言\r\n作者介绍\r\n\r\n第一部分 基本知识\r\n\r\n第1章 Delphi编程简介\r\n\r\n1.1 Delphi的产品家族介绍\r\n1.2 认识Delphi\r\n1.2.1 可视化开发环境的特性\r\n1.2.2 编译器速度和编译后代码执行效率\r\n1.2.3 编程语言的功能和复杂性的对立\r\n1.2.4 数据库结构的灵活性和可扩展性\r\n1.2.5 实现机制对设计和使用模式的增强\r\n1.3 历史回顾\r\n1.3.1 Delphi 1\r\n1.3.2 Delphi 2\r\n1.3.3 Delphi 3\r\n1.3.4 Delphi 4\r\n1.3.5 Delphi 5\r\n1.3.6 Delphi 6\r\n1.4 Delphi IDE环境\r\n1.4.1 主窗口\r\n1.4.2 主菜单\r\n1.4.3 Delphi工具栏\r\n1.4.4 组件选项板\r\n1.4.5 窗体设计器\r\n1.4.6 对象观察器\r\n1.4.7 代码编辑器\r\n1.4.8 代码浏览器\r\n1.4.9 对象层次图\r\n1.5 项目源文件概述\r\n1.6 应用程序简介\r\n1.7 事件处理机制的优势\r\n1.8 快速原型化\r\n1.9 可扩展的组件和环境\r\n1.10 必须掌握的10种IDE功能\r\n1.11 小结\r\n\r\n第2章 Object Pascal语言\r\n\r\n2.1 注释\r\n2.2 扩展过程和函数特性\r\n2.2.1 函数调用中的圆括号\r\n2.2.2 重载\r\n2.2.3 缺省参数值\r\n2.3 变量\r\n2.4 常量\r\n2.5 运算符\r\n2.5.1 赋值运算符\r\n2.5.2 比较运算符\r\n2.5.3 逻辑运算符\r\n2.5.4 数学运算符\r\n2.5.5 按位运算符\r\n2.5.6 增/减量运算\r\n2.5.7 “计算-赋值”运算符\r\n2.6 Object Pascal类型\r\n2.6.1 类型比较\r\n2.6.2 字符类型\r\n2.6.3 字符串\r\n2.6.4 变体类型\r\n2.6.5 货币类型\r\n2.7 用户自定义类型\r\n2.7.1 数组\r\n2.7.2 动态数组\r\n2.7.3 记录\r\n2.7.4 集合\r\n2.7.5 对象\r\n2.7.6 指针\r\n2.7.7 类型别名\r\n2.8 类型转换和强制类型转换\r\n2.9 字符串资源\r\n2.10 条件判断语句\r\n2.10.1 if语句\r\n2.10.2 case语句\r\n2.11 循环\r\n2.11.1 for循环\r\n2.11.2 while循环\r\n2.11.3 repeat..until循环\r\n2.11.4 Break()过程\r\n2.11.5 Continue()过程\r\n2.12 过程和函数\r\n2.13 作用域\r\n2.14 单元\r\n2.14.1 uses子句\r\n2.14.2 单元循环引用\r\n2.15 包\r\n2.15.1 使用Delphi包\r\n2.15.2 包的语法格式\r\n2.16 面向对象编程\r\n2.17 使用Delphi对象\r\n2.17.1 声明和实例化\r\n2.17.2 析构\r\n2.17.3 方法\r\n2.17.4 方法的类型\r\n2.17.5 属性\r\n2.17.6 可见区域说明符\r\n2.17.7 友元类\r\n2.17.8 对象的秘密\r\n2.17.9 TObject:所有对象的祖先\r\n2.17.10 接口\r\n2.18 结构化异常处理\r\n2.18.1 异常类\r\n2.18.2 异常执行流程\r\n2.18.3 异常的再激活\r\n2.19 运行时信息\r\n2.20 小结\r\n\r\n第3章 理解Windows消息\r\n\r\n3.1 什么是消息\r\n3.2 消息的类型\r\n3.3 Windows消息系统的工作方式\r\n3.4 Delphi的消息系统\r\n3.5 消息的处理\r\n3.5.1 消息处理:并非无约定\r\n3.5.2 为Result消息值赋值\r\n3.5.3 TApplication类型的OnMessage事件\r\n3.6 发送自己的消息\r\n3.6.1 Perform()方法\r\n3.6.2 SendMessage()和PostMessage()API函数\r\n3.7 非标准消息\r\n3.7.1 通知消息\r\n3.7.2 内部VCL消息\r\n3.7.3 用户定义消息\r\n3.8 剖析VCL的消息系统\r\n3.9 消息和事件之间的关系\r\n3.10 小结\r\n\r\n第二部分 高级技术\r\n\r\n第4章 编写可移植代码\r\n\r\n4.1 版本兼容的共性\r\n4.1.1 版本判断\r\n4.1.2 单元、组件和包\r\n4.1.3 关于IDE的讨论\r\n4.2 Delphi和Kylix兼容性\r\n4.2.1 Linux中不具备的特性\r\n4.2.2 编译器和语言特性\r\n4.2.3 平台相关特点\r\n4.3 Delphi 6的新特性\r\n4.3.1 变体变量\r\n4.3.2 枚举类型\r\n4.3.3 $IF指示符\r\n4.3.4 潜在的二进制DFM的不兼容\r\n4.4 从Delphi 5升级\r\n4.4.1 可写的类型常量\r\n4.4.2 Cardinal一元取反操作\r\n4.5 从Delphi 4升级\r\n4.5.1 RTL问题\r\n4.5.2 VCL问题\r\n4.5.3 Internet开发主题\r\n4.5.4 数据库问题\r\n4.6 从Delphi 3升级\r\n4.6.1 无符号32位整数\r\n4.6.2 64位整数\r\n4.6.3 Real类型\r\n4.7 从Delphi 2升级\r\n4.7.1 Boolean类型的改变\r\n4.7.2 ResourceString\r\n4.7.3 BTL的改变\r\n4.7.4 TCustomForm\r\n4.7.5 GetChildren()\r\n4.7.6 自动化服务器\r\n4.8 从Delphil升级\r\n4.9 小结\r\n\r\n第5章 多线程技术\r\n\r\n5.1 阐述线程\r\n5.1.1 多任务的类型\r\n5.1.2 在Delphi应用程序中使用多线程\r\n5.1.3 线程的滥用\r\n5.2 TThread对象\r\n5.2.1 TThread基础\r\n5.2.2 线程实例\r\n5.2.3 线程终止\r\n5.2.4 和VCL同步\r\n5.2.5 应用程序Demo\r\n5.2.6 优先级和调度\r\n5.2.7 线程的挂起和恢复\r\n5.2.8 线程中的计时\r\n5.3 管理多线程\r\n5.3.1 线程局部存储\r\n5.3.2 线程同步\r\n5.4 多线程应用程序范例\r\n5.4.1 用户界面\r\n5.4.2 搜索线程\r\n5.4.3 调整优先级\r\n5.5 多线程访问BDE\r\n5.6 多线程图形操作\r\n5.7 纤程\r\n5.8 小结\r\n\r\n第6章 动态链接库\r\n\r\n6.1 何谓DLL\r\n6.2 静态链接和动态链接\r\n6.3 为何要用DLL\r\n6.3.1 多个应用程序共享代码、资源和数据\r\n6.3.2 隐藏实施细节\r\n6.4 创建和使用DLL\r\n6.4.1 数美分(简单的DLL)\r\n6.4.2 通过DLL显示模式窗体\r\n6.5 通过DLL显示无模式窗体\r\n6.6 在Delphi应用程序中使用DLL\r\n6.7 显式调用DLL\r\n6.8 动态链接库的入口/出口函数\r\n6.8.1 进程和线程的初始化和结束标准函数\r\n6.8.2 DLL入口/出口示例\r\n6.9 DLL中的异常\r\n6.9.1 在16位Delphi中捕捉异常\r\n6.9.2 异常和Safecall指示符\r\n6.10 回调函数\r\n6.10.1 使用回调函数\r\n6.10.2 绘制Owner-Draw列表框\r\n6.11 通过DLL调用回调函数\r\n6.12 共享不同进程中的DLL数据\r\n6.12.1 创建共享内存的DLL\r\n6.12.2 使用共享内存的DLL\r\n6.13 从DLL中输出对象\r\n6.14 小结\r\n\r\n第三部分 数据库开发\r\n\r\n第7章 Delphi数据库体系结构\r\n\r\n7.1 数据库类型\r\n7.2 数据库体系结构\r\n7.3 连接到数据库服务器\r\n7.3.1 数据库连接概述\r\n7.3.2 建立数据库连接\r\n7.4 操作数据集\r\n7.4.1 打开及关闭数据集\r\n7.4.2 导航数据集\r\n7.4.3 管理数据集\r\n7.4.4 数据集状态\r\n7.5 操作字段\r\n7.5.1 字段值\r\n7.5.2 字段数据类型\r\n7.5.3 字段名称及号码\r\n7.5.4 管理字段数据\r\n7.5.5 操作BLOB字段\r\n7.5.6 过滤数据\r\n7.5.7 搜索数据集\r\n7.5.8 关键字查找\r\n7.5.9 使用数据模块\r\n7.5.10 查找、范围及过滤器演示\r\n7.5.11 书签\r\n7.6 小结\r\n\r\n第8章 用dbExpress进行数据库开发\r\n\r\n8.1 使用dbExpess\r\n8.1.1 单向、只读数据集\r\n8.1.2 dbExpress与BDE比较\r\n8.1.3 dbExpress支持跨平台开发\r\n8.2 dbExpress组件\r\n8.2.1 TSQLConnection\r\n8.2.2 TSQLDataset\r\n8.2.3 显示查询结果\r\n8.2.4 向后兼容组件\r\n8.2.5 TSQLMonitor\r\n8.3 设计可编辑的dbExpress应用程序\r\n8.4 部署dbExpress应用程序\r\n8.5 小结\r\n\r\n第9章 用dbGo for ADO进行数据库开发\r\n\r\n9.1 dbGo介绍\r\n9.2 Mcrosoft的统一数据访问策略概述\r\n9.3 OLE DB、ADO、ODBC概述\r\n9.4 使用dbGo for ADO\r\n9.4.1 为ODBC建立OLE DB提供者\r\n9.4.2 Access数据库\r\n9.5 dbGo for ADO组件\r\n9.5.1 TADOConnection\r\n9.5.2 建立数据库连接\r\n9.5.3 回避/替代登录提示\r\n9.5.4 TADOCommand\r\n9.5.5 TADODataset\r\n9.5.6 类似BDE的数据集组件\r\n9.6 事务处理\r\n9.7 小结\r\n\r\n第四部分 基于组件的开发\r\n\r\n第10章 组件体系:VCL和CLX\r\n\r\n10.1 关于新的CLX\r\n10.2 何谓组件\r\n10.3 组件的层次结构\r\n10.3.1 非可视组件\r\n10.3.2 可视组件\r\n10.4 组件结构\r\n10.4.1 属性\r\n10.4.2 属性的类型\r\n10.4.3 方法\r\n10.4.4 事件\r\n10.4.5 流式属性\r\n10.4.6 所有权\r\n10.4.7 父子关系\r\n10.5 可视组件的继承关系\r\n10.5.1 TPersistent类\r\n10.5.2 TPersistent方法\r\n10.5.3 TComponent类\r\n10.5.4 TControl类\r\n10.5.5 TWinControl和TWidgetControl\r\n10.5.6 TGraphicControl类\r\n10.5.7 TCustomControl类\r\n10.5.8 其他类\r\n10.6 运行期类型信息\r\n10.6.1 TypInfo.pas单元:运行类型信息的定义者\r\n10.6.2 获取类型信息\r\n10.6.3 获得方法指针的类型信息\r\n10.6.4 获取有序类型的类型信息\r\n10.6.5 通过RTTI为属性赋值\r\n10.7 小结\r\n\r\n第11章 编写VCL组件\r\n\r\n11.1 组件设计基础\r\n11.1.1 决定是否应编写组件\r\n11.1.2 编写组件的步骤\r\n11.1.3 确定祖先类\r\n11.1.4 创建组件单元\r\n11.1.5 创建属性\r\n11.1.6 创建事件\r\n11.1.7 创建自定义方法\r\n11.1.8 构造函数和析构函数\r\n11.1.9 注册组件\r\n11.1.10 测试组件\r\n11.1.11 提供组件图标\r\n11.2 组件示例\r\n11.2.1 扩展Win32组件包装器的性能\r\n11.2.2 TddgRunButton:创建属性\r\n11.3 TddgButtonEdit:一个容器组件\r\n11.3.1 设计决策\r\n11.3.2 显露属性\r\n11.3.3 显露事件\r\n11.3.4 TddgDigitalClock:创建组件事件\r\n11.3.5 把窗体添加到组件面板\r\n11.4 小结\r\n\r\n第12章 高级VCL组件构造技术\r\n\r\n12.1 伪可视组件\r\n12.1.1 扩展提示功能\r\n12.1.2 创建一个THintWindow派生类\r\n12.1.3 椭圆型窗口\r\n12.1.4 激活THintWindow派生窗口\r\n12.1.5 运用TDDGHintWindow\r\n12.2 动态组件\r\n12.2.1 滚动字幕组件\r\n12.2.2 编写组件\r\n12.2.3 在屏幕以外的位图上绘制\r\n12.2.4 描绘组件\r\n12.2.5 让组件“动起来”\r\n12.2.6 测试TddgMarquee组件\r\n12.3 编写属性编辑器\r\n12.3.1 创建一个继承的属性编辑器对象\r\n12.3.2 将属性作为文本进行编辑\r\n12.3.3 注册新的属性编辑器\r\n12.3.4 利用对话框将属性作为整体进行编辑\r\n12.4 组件编辑器\r\n12.4.1 TComponentEditor\r\n12.4.2 一个简单的组件\r\n12.4.3 一个简单的组件编辑器\r\n12.4.4 注册组件编辑器\r\n12.5 对非公开的组件数据执行流操作\r\n12.5.1 定义属性\r\n12.5.2 DefineProperty()的例子\r\n12.5.3 TddgWaveFile:DefineBinaryProperty()用法示例\r\n12.6 属性类别\r\n12.6.1 类别类\r\n12.6.2 自定义类别\r\n12.7 组件列表:TCollection和TCollectionItem\r\n12.7.1 定义TCollectionItem类:TRunBtnItem\r\n12.7.2 定义TCollection类:TRunButtons\r\n12.7.3 实现TddgLaunchPad、TRun-BtnItem和TRunButtons\r\n12.7.4 用对话框属性编辑器编辑TCollectionItem组件的列表\r\n12.8 小结\r\n\r\n第13章 CLX组件开发\r\n\r\n13.1 何谓CLX\r\n13.2 CLX的体系结构\r\n13.3 移植问题\r\n13.4 组件示例\r\n13.4.1 TddgSpinner组件\r\n13.4.2 设计期增强工具\r\n13.4.3 组件引用和图像列表\r\n13.4.4 CLX数据感知组件\r\n13.5 CLX设计编辑器\r\n13.6 包\r\n13.6.1 命名约定\r\n13.6.2 运行期包\r\n13.6.3 设计期包\r\n13.6.4 单元注册\r\n13.6.5 组件位图\r\n13.7 小结\r\n\r\n第14章 充分发挥包的作用\r\n\r\n14.1 为何要用包\r\n14.1.1 精简代码\r\n14.1.2 发布更小的应用程序--应用程序分割\r\n14.1.3 组件容器\r\n14.2 为何不用包\r\n14.3 包的类型\r\n14.4 包文件\r\n14.5 使用运行期包\r\n14.6 把包安装到Delphi IDE中\r\n14.7 创建包\r\n14.7.1 包编辑器\r\n14.7.2 包设计方案\r\n14.8 包的版本化\r\n14.9 包编译器指示符\r\n14.10 包的命名约定\r\n14.11 使用运行期(插件)包的可扩展应用程序\r\n14.12 从包中导出函数\r\n14.13 获取包的信息\r\n14.14 小结\r\n\r\n第15章 COM开发\r\n\r\n15.1 COM基础\r\n15.1.1 COM2组件对象模型\r\n15.1.2 COM、ActiveX和OLE之间的对比\r\n15.1.3 术语\r\n15.1.4 ActiveX的优势\r\n15.1.5 OLE 与OLE 2的对比\r\n15.1.6 结构化存储\r\n15.1.7 统一数据传输\r\n15.1.8 线程模式\r\n15.1.9 COM+\r\n15.2 COM与Object Pascal\r\n15.2.1 接口\r\n15.2.2 使用接口\r\n15.2.3 HResult返回类型\r\n15.3 COM对象和类工厂\r\n15.3.1 TComObject和TComObject-Factory\r\n15.3.2 in-process COM服务器\r\n15.3.3 创建in-proc COM服务器实例\r\n15.3.4 out-of-process COM服务器\r\n15.4 聚合\r\n15.5 分布式COM\r\n15.6 Automation\r\n15.6.1 IDispatch\r\n15.6.2 类型信息\r\n15.6.3 后期绑定与早期绑定的对比\r\n15.6.4 注册\r\n15.6.5 创建Automation服务器\r\n15.6.6 创建Automation控制器\r\n15.7 高级Automation技术\r\n15.7.1 Automation事件\r\n15.7.2 Automation集合\r\n15.7.3 类型库新增接口类型\r\n15.7.4 二进制数据的交换\r\n15.7.5 后台:COM的语言支持\r\n15.8 TOleContainer\r\n15.8.1 一个简单的示例程序\r\n15.8.2 一个较为复杂的示例程序\r\n15.9.4 小结\r\n\r\n第16章 Windows外壳编程\r\n\r\n16.1 托盘图标组件\r\n16.1.1 API\r\n16.1.2 处理消息\r\n16.1.3 图标及提示\r\n16.1.4 鼠标点击\r\n16.1.5 隐藏应用程序\r\n16.1.6 托盘图标应用程序示例\r\n16.2 应用程序桌面工具栏\r\n16.2.1 API\r\n16.2.2 TAppBar:AppBar的窗体\r\n16.2.3 使用TAppBar\r\n16.3 外壳链接\r\n16.3.1 获取IShellLink实例\r\n16.3.2 使用IShellLink\r\n16.3.3 创建一个外壳链接\r\n16.3.4 获取及设置链接信息\r\n16.3.5 示例程序\r\n16.4 外壳扩展\r\n16.4.1 COM对象向导\r\n16.4.2 复制钩子处理器\r\n16.4.3 环境菜单处理器\r\n16.4.4 图标处理器\r\n16.4.5 信息提示处理器\r\n16.5 小结\r\n\r\n第17章 使用Open Tools API\r\n\r\n17.1 Open Tools接口\r\n17.2 使用Open Tools API\r\n17.2.1 简单的向导\r\n17.2.2 Wizard向导\r\n17.2.3 DDG Search\r\n17.3 窗体向导\r\n17.4 小结\r\n\r\n第五部分 企业开发\r\n\r\n第18章 使用COM+/MTS开发事务程序\r\n\r\n18.1 什么是COM+\r\n18.2 使用COM的原因\r\n18.3 服务\r\n18.3.1 事务\r\n18.3.2 安全\r\n18.3.3 及时激活\r\n18.3.4 队列组件\r\n18.3.5 对象缓冲池\r\n18.3.6 事件\r\n18.4 运行期\r\n18.4.1 注册数据库(RegDB)\r\n18.4.2 配置组件\r\n18.4.3 运行环境(Contexts)\r\n18.4.4 Neutral线程\r\n18.5 创建COM+应用程序\r\n18.5.1 目标:规模\r\n18.5.2 执行环境\r\n18.5.3 有状态(Stateful)和无状态(Stateless)\r\n18.5.4 生命周期管理\r\n18.5.5 COM+应用程序的组织\r\n18.5.6 有关事务的思考\r\n18.5.7 资源\r\n18.6 Delphi中的COM+\r\n18.6.1 COM+向导\r\n18.6.2 COM+框架\r\n18.6.3 Tic-Tac-Toe:一个简单的应用程序\r\n18.6.4 调试COM+应用程序\r\n18.7 小结\r\n\r\n第19章 CORBA开发\r\n\r\n19.1 CORBA特性\r\n19.2 CORBA体系结构\r\n19.2.1 OSAgent\r\n19.2.2 接口\r\n19.3 接口定义语言\r\n19.3.1 基本类型\r\n19.3.2 用户定义类型\r\n19.3.3 别名\r\n19.3.4 枚举\r\n19.3.5 结构\r\n19.3.6 数组\r\n19.3.7 序列\r\n19.3.8 方法参数\r\n19.3.9 模块\r\n19.4 银行案例\r\n19.5 复杂数据类型\r\n19.6 Delphi、CORBA和Enterprise Java Beans(EJB)\r\n19.6.1 Delphi程序员EJB速成\r\n19.6.2 EJB是一种特殊的组件\r\n19.6.3 EJB在容器中运行\r\n19.6.4 EJB具有预定义的API\r\n19.6.5 Home(本地)接口和Remote(远程)接口\r\n19.6.6 EJB的类型\r\n19.6.7 配置JBuilder 5以开发EJB\r\n19.6.8 创建一个简单的“Hello'world” EJB\r\n19.7 CORBA和Web Services\r\n19.7.1 创建Web Service\r\n19.7.2 创建SOAP客户端应用程序\r\n19.7.3 添加CORBA客户端代码至Web Service中\r\n19.8 小结\r\n\r\n第20章 BizSnap开发:基于SOAP的Web Services\r\n\r\n20.1 Web Services\r\n20.2 SOAP\r\n20.3 编写Web Service\r\n20.3.1 TWebModule\r\n20.3.2 定义一个可调用接口\r\n20.3.3 实现一个可调用接口\r\n20.3.4 测试Web Service\r\n20.4 从客户端调用Web Service\r\n20.4.1 为远程可调用对象产生一个输入单元\r\n20.4.2 使用THTTPRIO组件\r\n20.5 小结\r\n\r\n第21章 DataSnap开发\r\n\r\n21.1 创建多层应用程序的机制\r\n21.2 多层体系结构的优点\r\n21.2.1 集中商业逻辑\r\n21.2.2 瘦客户结构\r\n21.2.3 自动错误调解\r\n21.2.4 公文包模型\r\n21.2.5 容错性\r\n21.2.6 负载平衡\r\n21.3 典型的DataSnap结构\r\n21.3.1 服务器\r\n21.3.2 客户端\r\n21.4 使用DataSnap创建应用程序\r\n21.4.1 创建服务器\r\n21.4.2 创建客户端\r\n21.5 更多增强应用程序健壮性的措施\r\n21.5.1 客户端优化技术\r\n21.5.2 应用程序服务器技术\r\n21.6 实例\r\n21.7 Client Dataset组件的更多特性\r\n21.8 典型错误\r\n21.9 配置DataSnap应用程序\r\n21.9.1 发放许可证\r\n21.9.2 DCOM配置\r\n21.9.3 文件配置\r\n21.9.4 Internet配置考虑(防火墙)\r\n21.10 小结\r\n\r\n第六部分 Internet开发\r\n\r\n第22章 ASP开发\r\n\r\n22.1 理解活动服务器对象\r\n22.2 活动服务器对象向导\r\n22.2.1 类型库编辑器\r\n22.2.2 新方法\r\n22.2.3 ASP Response对象\r\n22.2.4 第一次运行\r\n22.2.5 ASP Request对象\r\n22.2.6 重新编译活动服务器对象\r\n22.2.7 再次运行活动服务器对象\r\n22.3 ASP Session、Server和Application对象\r\n22.4 活动服务器对象及数据库\r\n22.5 活动服务器对象及NetCLX支持\r\n22.6 调试活动服务器对象\r\n22.6.1 使用MTS调试活动服务器对象\r\n22.6.2 采用Windows NT 4调试\r\n22.6.3 采用Windows 2000调试\r\n22.7 小结\r\n\r\n第23章 WebSnap开发\r\n\r\n23.1 WebSnap的特性\r\n23.1.1 多个Webmodule\r\n23.1.2 服务器执行脚本\r\n23.1.3 TAdapter(适配器)组件\r\n23.1.4 多种调度方法\r\n23.1.5 Page producer(页面生成器)组件\r\n23.1.6 会话管理\r\n23.1.7 登录服务\r\n23.1.8 用户跟踪\r\n23.1.9 HTML管理\r\n23.1.10 文件上传服务\r\n23.2 websnap开发\r\n23.2.1 应用程序的设计\r\n23.2.2 为应用程序添加功能\r\n23.2.3 导航菜单栏\r\n23.2.4 登录\r\n23.2.5 管理用户偏好数据\r\n23.2.6 在会话之间保存偏好数据\r\n23.2.7 图像处理\r\n23.2.8 显示数据\r\n23.2.9 将应用程序转换为ISAPI DLL\r\n23.3 高级话题\r\n23.3.1 LocabFileServices(文件定位服务)\r\n23.3.2 文件上传\r\n23.3.3 包含定制模板\r\n23.3.4 在TAdapterPageProducer中定制组件\r\n23.4 小结\r\n\r\n第24章 无线开发\r\n\r\n24.1 开发技术的发展:回顾\r\n24.1.1 20世纪80年代以前:庞然大物时代\r\n24.1.2 20世纪80年代后期:桌面数据库应用程序\r\n24.1.3 20世纪90年代早期:客户端/服务器\r\n24.1.4 20世纪90年代后期:多层结构及基于Internet的事务处理\r\n24.1.5 21世纪早期:扩展到无线移动领域的应用程序基础架构\r\n24.2 移动无线设备\r\n24.1.3 20世纪90年代早期:客户端/服务器\r\n24.1.4 20世纪90年代后期:多层结构及基于Internet的事务处理\r\n24.1.5 21世纪早期:扩展到无线移动领域的应用程序基础架构\r\n24.2 移动无线设备\r\n24.2.1 移动电话\r\n24.2.2 palmos设备\r\n24.2.3 Pocket PC\r\n24.2.4 RIM BlackBerrv\r\n24.3 无线电技术\r\n24.3.1 GSM、CDMA和TDMA\r\n24.3.2 CDPD\r\n24.3.3 3G\r\n24.3.4 GPRS\r\n24.3.5 蓝牙\r\n24.3.6 802.11\r\n24.4 基于服务器的无线数据技术\r\n24.4.1 SMS\r\n24.4.2 WAP\r\n24.4.3 I-mode\r\n24.4.4 PQA\r\n24.5 无线用户体验\r\n24.5.1 环路选择网络和数据包选择网络\r\n24.5.2 无线并非Web\r\n24.5.3 窗体因素的重要性\r\n24.5.4 数据输入和导航技术\r\n24.5.5 M-Commerce\r\n24.6 小结 本书至今已增订到第5版,在7年的时间中,本书不断完善,为了写作本书,很多人对此付出了相当多的精力。Xavier和Steve是最早的Borland Delphi开发小组成员,本书是他们结合15年的Delphi开发经验写成的。在本书中,我们可以感受到作者的努力精神,正是这种精神,让Delphi开发从书成为世界上最畅销的Delphi书籍,并两次获得Delphi读者最佳选择奖。这本书由开发人员撰写,同时也供开发人员阅读。 本书建立在《Delphi 5开发人员指南》基础之上,理想状况下,我们希望本书包括《Delphi 5开发人员指南》的全部章节和所有新内容。但是《Delphi 5开发人员指南》一书本身太厚,为了给Delphi 6新特性的讨论留下足够空间,我们只抽取了其中的部分章节,再加上Delphi 6的新特性讨论,新书就这样展现在大家面前。 本书包括了一些全新章节,许多章节是《Delphi 5开发人员指南》对应章节的扩展。但在《Delphi 5开发人员指南》中介绍的内容还是极其有用的。因此我们在本书配套光盘中附带了整个电子版的《Delphi 5开发人员指南》,每个章节单独地用PDF格式存储。在电子版的封面中包含了《Delphi 5开发人员指南》的目录表,读者可以在其中查找感兴趣的章节。对读者而言,可谓一举两得。 本书分成六部分。第一部分,“基础知识”,讲述了Delphi编程必要的基础知识。第二部分,“高级技术”,讨论了一些通用的高级开发主题,例如线程和动态链接库。第三部分,“数据库开发”,从多个层面上讨论了Delphi数据库访问主题。第四部分,“基于组件的开发”,讨论了关于VCL、CLX、包、COM和Open Tools API开发问题。第五部分,“企业开发”,其中讨论了开发企业级应用程序需要的实践知识,包括COM+、COBRA、SOAP/BizSnap和DataSnap。最后,第六部分,“Internet开发”,演示了使用Delphi开发Internet和无线应用程序的技术。 本书面向的读者 书如其名,本书是面向开发人员的。如果读者是一位开发人员,并且使用Delphi,那就需要拥有一本这样的书。本书面向3类读者: ■希望将技术提高一个层次的Delphi开发人员。 ■熟悉Pascal、C/C++、Java或Basic,并希望开始使用Delphi的程序员。 ■希望最大限度利用Delphi来研究其中先进特性和隐含特性的程序员。 本书使用的约定 本书使用了下列约定: ■代码行、命令、声明、变量、程序输出以及任何屏幕内容都用计算机打印字体显示。 ■需要读者输入的内容用粗体显示。 ■语法描述中的占位符用斜体表示。 ■技术术语在文章中第一次出现或者用于强调重点时,用斜体表示。 ■程序或过程用在其名后加上一对圆括号表示。虽然这不是标准Pascal风格,但是有助于和属性、变量和类区分。 各章都有一些注意、提示和警告,为了方便阅读,用特殊的字体标出。 本书配套光盘中有书中所有的源代码和项目文件,同时还有一些本书中尚未提到的源代码示例。此外,光盘中还包括一些第三方组件和工具的使用版。 Delphi 6开发指南网站 请访问网站http://www.xapware.com/ddg并且加入《Delphi 6开发人员指南》会员,从而得到本书的额外信息。读者也可以加入我们新闻邮件的邮件列表,并访问我们的讨论组。 写作本书的缘由 有人会问,是什么驱使我们编写本书的。这很难解释,但是当看到其他开发人员仍然将标满了注解、且被翻阅得破烂不堪的《Delphi 5开发人员指南》视为至宝时,就感到一切付出都是值得的。 完成本书的写作后,现在我们可以稍微轻松下来并愉快地编写一些Delphi程序。本书开始讲述的节奏比较慢,但是会迅速深入到Delphi的高级主题中,不过读者应该能够适应这种节奏。如果读者意识到了这一点,说明您已经通读了本书,并且具有了足够的Delphi方面的知识与技术,也许已经可以被称作Delphi专家了
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页