用 Go 语言轻松构建 MCP 客户端与服务器

简介: 本文介绍了如何使用 mcp-go 构建一个完整的 MCP 应用,包括服务端和客户端两部分。- 服务端支持注册工具(Tool)、资源(Resource)和提示词(Prompt),并可通过 stdio 或 sse 模式对外提供服务;- 客户端通过 stdio 连接服务器,支持初始化、列出服务内容、调用远程工具等操作。

该文章已被 Model Context Protocol(MCP) 中文教程讲解 收录,欢迎 star 收藏。

若想获取可执行的完整项目代码,可关注公众号:程序员陈明勇,回复 MCP

前言

模型上下文协议(Model Context Protocol,简称 MCP)是一种开放标准,旨在标准化大型语言模型(LLM)与外部数据源和工具之间的交互方式。随着 MCP 越来越受欢迎,Go MCP 库应运而生。本文将介绍如何在 Go 语言里面轻松构建 MCP 客户端和服务器。

如果你不熟悉 MCP 协议,可以看我之前写的这篇文章:一文掌握 MCP 上下文协议:从理论到实践

准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。

image.png

mcp-go

要构建 MCP 客户端和服务器,我们需要使用 mcp-go 库。

mcp-goGo 语言实现的 Model Context ProtocolMCP)库,通过这个库可以实现 LLM 应用与外部数据源和工具之间的无缝集成。

主要特点

  • 快速:高级接口意味着更少的代码和更快的开发速度

  • 简单:使用极少的样板代码构建 MCP 服务器

  • 完整:MCP Go 旨在提供 MCP 核心规范的完整实现

安装 MCP 库

Go 项目根目录下,执行以下命令:

go get github.com/mark3labs/mcp-go

构建 MCP 服务器

接下来,我们使用 mcp-go 提供的 server 模块,构建一个通过 stidio 方式连接的 MCP 服务器。

创建 server 对象

s := server.NewMCPServer(
    "Server Demo",
    "1.0.0",
)

创建 server 对象时,我们可以指定 服务器名版本号 等参数。

添加工具(tools)

以下是一个示例,用于创建并注册一个简单的计算器工具:

calculatorTool := mcp.NewTool("calculate",
    mcp.WithDescription("执行基本的算术运算"),
    mcp.WithString("operation",
        mcp.Required(),
        mcp.Description("要执行的算术运算类型"),
        mcp.Enum("add", "subtract", "multiply", "divide"), // 保持英文
    ),
    mcp.WithNumber("x",
        mcp.Required(),
        mcp.Description("第一个数字"),
    ),
    mcp.WithNumber("y",
        mcp.Required(),
        mcp.Description("第二个数字"),
    ),
)

s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
   
    op := request.Params.Arguments["operation"].(string)
    x := request.Params.Arguments["x"].(float64)
    y := request.Params.Arguments["y"].(float64)

    var result float64
    switch op {
   
    case "add":
        result = x + y
    case "subtract":
        result = x - y
    case "multiply":
        result = x * y
    case "divide":
        if y == 0 {
   
            return nil, errors.New("不允许除以零")
        }
        result = x / y
    }

    return mcp.FormatNumberResult(result), nil
})

添加工具的步骤如下:

  • 创建工具对象
    使用 mcp.NewTool 创建一个工具实例。

    • 第一个参数是工具名称(必须),例如 "calculate"
    • 其余参数通过函数选项(functional options)方式传入,例如:
      • mcp.WithDescription(...) 添加工具描述;
      • mcp.WithString(...)mcp.WithNumber(...) 定义参数及其规则(如是否必填、参数说明、枚举限制等)。
  • 注册工具到服务器
    通过 s.AddTool 方法将工具注册到 MCP 服务中。

    • 第一个参数是上一步创建的工具对象;
    • 第二个参数是该工具的处理函数(handler),用于实现工具的具体逻辑,如参数解析、运算执行、返回结果等。

添加资源(Resources)

下面的示例展示了如何创建并注册一个静态资源,用于读取并提供 README.md 文件的内容。

resource := mcp.NewResource(
    "docs://readme",
    "项目说明文档",
    mcp.WithResourceDescription("项目的 README 文件"),
    mcp.WithMIMEType("text/markdown"),
)

s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
   
    content, err := os.ReadFile("README.md")
    if err != nil {
   
        return nil, err
    }

    return []mcp.ResourceContents{
   
        mcp.TextResourceContents{
   
            URI:      "docs://readme",
            MIMEType: "text/markdown",
            Text:     string(content),
        },
    }, nil
})

添加资源的步骤如下:

  • 创建资源对象
    使用 mcp.NewResource 函数创建资源实例。

    • 第一个参数为资源 URI,用于标识资源;
    • 第二个参数为资源名称;
    • 通过函数选项补充更多信息,例如:
      • mcp.WithResourceDescription(...) 设置资源描述;
      • mcp.WithMIMEType(...) 指定资源的 MIME 类型。
  • 注册资源处理函数
    使用 s.AddResource 将资源对象注册到服务器,并提供一个处理函数:

    • 该处理函数会在资源被访问时执行;
    • 返回值是资源内容的数组(例如读取本地文件内容并封装为 TextResourceContents)。

添加提示词(Prompts)

以下示例展示了如何创建并添加一个带参数的简单提示词,用于生成个性化的问候语。

s.AddPrompt(mcp.NewPrompt("greeting",
    mcp.WithPromptDescription("一个友好的问候提示"),
    mcp.WithArgument("name",
        mcp.ArgumentDescription("要问候的人的名字"),
    ),
), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
   
    name := request.Params.Arguments["name"]
    if name == "" {
   
        name = "朋友"
    }

    return mcp.NewGetPromptResult(
        "友好的问候",
        []mcp.PromptMessage{
   
            mcp.NewPromptMessage(
                mcp.RoleAssistant,
                mcp.NewTextContent(fmt.Sprintf("你好,%s!今天有什么可以帮您的吗?", name)),
            ),
        },
    ), nil
})

添加提示词的步骤如下:

  • 创建提示词对象
    通过 mcp.NewPrompt 创建一个提示词定义。

    • 第一个参数是提示词名称;
    • 可通过 mcp.WithPromptDescription(...) 添加描述;
    • 使用 mcp.WithArgument(...) 定义参数及其说明(如提示词中需要动态插值的内容)。
  • 注册提示词处理函数
    使用 s.AddPrompt 将提示词对象注册到服务器,并提供对应的处理逻辑函数:

    • 函数接收用户输入参数;
    • 返回一个结构化的提示词响应(如构造一个带有用户名字的问候消息)。

启动基于 stdio 传输类型的服务器

// 启动基于 stdio 的服务器
if err := server.ServeStdio(s); err != nil {
   
    fmt.Printf("Server error: %v\n", err)
}

使用 server.ServeStdio 方法可以启动一个基于标准输入/输出(stdio)的 MCP 服务器。

这种方式适用于本地集成与命令行工具。

启动基于 sse(Server-Sent Events)传输类型的服务器

如果需要通过 HTTP 的方式提供服务,支持服务端推送数据,可以使用 SSE(Server-Sent Events)传输模式。

s := server.NewMCPServer(
    "My Server", // Server 名称
    "1.0.0",     // 版本号
)

// 创建基于 SSE 的服务器实例
sseServer := server.NewSSEServer(s)

// 启动服务器,监听指定端口(如 :8080)
err := sseServer.Start(":8080")
if err != nil {
   
    panic(err)
}

stdio 不同,sse 模式基于 HTTP 协议,更适合 Web 应用中的长连接场景,支持服务端推送数据。

完整的 stdio 代码示例

package main

import (
    "context"
    "errors"
    "fmt"
    "os"

    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
)

func main() {
   
    s := server.NewMCPServer(
        "Server Demo",
        "1.0.0",
    )

    // 添加工具
    {
   
        calculatorTool := mcp.NewTool("calculate",
            mcp.WithDescription("执行基本的算术运算"),
            mcp.WithString("operation",
                mcp.Required(),
                mcp.Description("要执行的算术运算类型"),
                mcp.Enum("add", "subtract", "multiply", "divide"), // 保持英文
            ),
            mcp.WithNumber("x",
                mcp.Required(),
                mcp.Description("第一个数字"),
            ),
            mcp.WithNumber("y",
                mcp.Required(),
                mcp.Description("第二个数字"),
            ),
        )

        s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
   
            op := request.Params.Arguments["operation"].(string)
            x := request.Params.Arguments["x"].(float64)
            y := request.Params.Arguments["y"].(float64)

            var result float64
            switch op {
   
            case "add":
                result = x + y
            case "subtract":
                result = x - y
            case "multiply":
                result = x * y
            case "divide":
                if y == 0 {
   
                    return nil, errors.New("不允许除以零")
                }
                result = x / y
            }

            return mcp.FormatNumberResult(result), nil
        })
    }

    // 添加资源
    {
   
        // 静态资源示例 - 暴露一个 README 文件
        resource := mcp.NewResource(
            "docs://readme",
            "项目说明文档",
            mcp.WithResourceDescription("项目的 README 文件"),
            mcp.WithMIMEType("text/markdown"),
        )

        // 添加资源及其处理函数
        s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
   
            content, err := os.ReadFile("README.md")
            if err != nil {
   
                return nil, err
            }

            return []mcp.ResourceContents{
   
                mcp.TextResourceContents{
   
                    URI:      "docs://readme",
                    MIMEType: "text/markdown",
                    Text:     string(content),
                },
            }, nil
        })
    }

    // 添加提示词
    {
   
        // 简单问候提示
        s.AddPrompt(mcp.NewPrompt("greeting",
            mcp.WithPromptDescription("一个友好的问候提示"),
            mcp.WithArgument("name",
                mcp.ArgumentDescription("要问候的人的名字"),
            ),
        ), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
   
            name := request.Params.Arguments["name"]
            if name == "" {
   
                name = "朋友"
            }

            return mcp.NewGetPromptResult(
                "友好的问候",
                []mcp.PromptMessage{
   
                    mcp.NewPromptMessage(
                        mcp.RoleAssistant,
                        mcp.NewTextContent(fmt.Sprintf("你好,%s!今天有什么可以帮您的吗?", name)),
                    ),
                },
            ), nil
        })
    }

    // 启动基于 stdio 的服务器
    if err := server.ServeStdio(s); err != nil {
   
        fmt.Printf("Server error: %v\n", err)
    }

}

构建 MCP 客户端

接下来,我们使用 mcp-go 提供的 client 模块,构建一个通过 stdio 方式连接到前面打包好的 MCP 服务器的客户端。

该客户端将展示以下功能:

  • 初始化客户端并连接服务器
  • 获取提示词、资源、工具列表
  • 调用远程工具(tool)

创建 MCP 客户端

mcpClient, err := client.NewStdioMCPClient(
    "./client/server", // 服务器可执行文件路径
    []string{
   },        // 启动参数(如果有)
)
if err != nil {
   
    panic(err)
}
defer mcpClient.Close()

通过 client.NewStdioMCPClient 方法可以创建一个基于 stdio 传输的客户端,并连接到指定的 MCP 服务器可执行文件。

初始化客户端连接

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

initRequest := mcp.InitializeRequest{
   }
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
initRequest.Params.ClientInfo = mcp.Implementation{
   
    Name:    "Client Demo",
    Version: "1.0.0",
}

initResult, err := mcpClient.Initialize(ctx, initRequest)
if err != nil {
   
    panic(err)
}
fmt.Printf("初始化成功,服务器信息: %s %s\n", initResult.ServerInfo.Name, initResult.ServerInfo.Version)

初始化操作通过 Initialize 方法完成,需指定协议版本及客户端信息。


获取提示词(Prompts)列表

promptsRequest := mcp.ListPromptsRequest{
   }
prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
if err != nil {
   
    panic(err)
}
for _, prompt := range prompts.Prompts {
   
    fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
    fmt.Println("参数:", prompt.Arguments)
}

客户端可以使用 ListPrompts 获取服务器上定义的所有提示词,包括名称、描述和参数结构。

获取资源(Resources)列表

resourcesRequest := mcp.ListResourcesRequest{
   }
resources, err := mcpClient.ListResources(ctx, resourcesRequest)
if err != nil {
   
    panic(err)
}
for _, resource := range resources.Resources {
   
    fmt.Printf("- uri: %s, name: %s, description: %s, MIME类型: %s\n",
        resource.URI, resource.Name, resource.Description, resource.MIMEType)
}

通过 ListResources 方法,客户端可以查看服务器上可用的静态或动态资源信息。

获取工具(Tools)列表

toolsRequest := mcp.ListToolsRequest{
   }
tools, err := mcpClient.ListTools(ctx, toolsRequest)
if err != nil {
   
    panic(err)
}
for _, tool := range tools.Tools {
   
    fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
    fmt.Println("参数:", tool.InputSchema.Properties)
}

通过 ListTools,客户端可以获取所有注册的工具信息,方便用户交互式选择或自动生成表单调用。

调用工具(Tool)

toolRequest := mcp.CallToolRequest{
   
    Request: mcp.Request{
   
        Method: "tools/call",
    },
}
toolRequest.Params.Name = "calculate"
toolRequest.Params.Arguments = map[string]any{
   
    "operation": "add",
    "x":         1,
    "y":         1,
}

result, err := mcpClient.CallTool(ctx, toolRequest)
if err != nil {
   
    panic(err)
}
fmt.Println("调用工具结果:", result.Content[0].(mcp.TextContent).Text)

通过构造 CallToolRequest,客户端可以向 MCP 服务器发起工具调用请求,并获取返回的结构化结果。

在此示例中,我们调用了服务器端注册的 calculate 工具,实现 1 + 1 运算。

完整代码示例

package main

import (
    "context"
    "fmt"
    "time"

    "github.com/mark3labs/mcp-go/client"
    "github.com/mark3labs/mcp-go/mcp"
)

func main() {
   

    // 创建一个基于 stdio 的MCP客户端
    mcpClient, err := client.NewStdioMCPClient(
        "./client/server",
        []string{
   },
    )
    if err != nil {
   
        panic(err)
    }
    defer mcpClient.Close()

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    fmt.Println("初始化 mcp 客户端...")
    initRequest := mcp.InitializeRequest{
   }
    initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
    initRequest.Params.ClientInfo = mcp.Implementation{
   
        Name:    "Client Demo",
        Version: "1.0.0",
    }

    // 初始化MCP客户端并连接到服务器
    initResult, err := mcpClient.Initialize(ctx, initRequest)
    if err != nil {
   
        panic(err)
    }
    fmt.Printf(
        "\n初始化成功,服务器信息: %s %s\n\n",
        initResult.ServerInfo.Name,
        initResult.ServerInfo.Version,
    )

    // 从服务器获取提示词列表
    fmt.Println("提示词列表:")
    promptsRequest := mcp.ListPromptsRequest{
   }
    prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
    if err != nil {
   
        panic(err)
    }
    for _, prompt := range prompts.Prompts {
   
        fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
        fmt.Println("参数:", prompt.Arguments)
    }

    // 从服务器获取资源列表
    fmt.Println()
    fmt.Println("资源列表:")
    resourcesRequest := mcp.ListResourcesRequest{
   }
    resources, err := mcpClient.ListResources(ctx, resourcesRequest)
    if err != nil {
   
        panic(err)
    }
    for _, resource := range resources.Resources {
   
        fmt.Printf("- uri: %s, name: %s, description: %s, MIME类型: %s\n", resource.URI, resource.Name, resource.Description, resource.MIMEType)
    }

    // 从服务器获取工具列表
    fmt.Println()
    fmt.Println("可用工具列表:")
    toolsRequest := mcp.ListToolsRequest{
   }
    tools, err := mcpClient.ListTools(ctx, toolsRequest)
    if err != nil {
   
        panic(err)
    }

    for _, tool := range tools.Tools {
   
        fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
        fmt.Println("参数:", tool.InputSchema.Properties)
    }
    fmt.Println()

    // 调用工具
    fmt.Println("调用工具: calculate")
    toolRequest := mcp.CallToolRequest{
   
        Request: mcp.Request{
   
            Method: "tools/call",
        },
    }
    toolRequest.Params.Name = "calculate"
    toolRequest.Params.Arguments = map[string]any{
   
        "operation": "add",
        "x":         1,
        "y":         1,
    }
    // Call the tool
    result, err := mcpClient.CallTool(ctx, toolRequest)
    if err != nil {
   
        panic(err)
    }
    fmt.Println("调用工具结果:", result.Content[0].(mcp.TextContent).Text)
}

若想获取可执行的完整项目代码,可关注公众号:程序员陈明勇,回复 MCP

小结

本文介绍了如何使用 mcp-go 构建一个完整的 MCP 应用,包括服务端和客户端两部分。

  • 服务端支持注册工具(Tool)、资源(Resource)和提示词(Prompt),并可通过 stdiosse 模式对外提供服务;
  • 客户端通过 stdio 连接服务器,支持初始化、列出服务内容、调用远程工具等操作。

你好,我是陈明勇,一名热爱技术、乐于分享的开发者,同时也是开源爱好者。

我专注于分享 Go 语言相关的技术知识,同时也会深入探讨 AI 领域的前沿技术。

成功的路上并不拥挤,有没有兴趣结个伴?

目录
相关文章
|
1月前
|
人工智能 API 开发者
FastAPI开发者福音!FastAPI-MCP:将FastAPI秒变MCP服务器的开源神器,无需配置自动转换!
FastAPI-MCP是一款能将FastAPI应用端点自动转换为符合模型上下文协议(MCP)的开源工具,支持零配置自动发现接口并保留完整文档和模式定义。
826 71
FastAPI开发者福音!FastAPI-MCP:将FastAPI秒变MCP服务器的开源神器,无需配置自动转换!
|
5天前
|
人工智能 监控 数据挖掘
5个开源MCP服务器:扩展AI助手能力,高效处理日常工作
AI大语言模型虽强大,但其原生能力仅限于文本对话,难以直接与外部世界交互。MCP(Model Context Protocol)服务器技术作为桥梁,赋予AI实质性环境交互能力,如浏览网页、分析数据等。本文基于实际经验,精选五种开源MCP服务器实现:Stagehand用于网络内容提取;Jupyter适用于数据分析;Opik提供AI行为监控;GitHub集成代码仓库管理;FastAPI-MCP支持自定义API集成。这些工具免费且可定制,为构建实用AI系统奠定基础。文章还提供了配置指南和应用场景剖析,助读者快速上手。
184 3
5个开源MCP服务器:扩展AI助手能力,高效处理日常工作
|
1月前
|
缓存 人工智能 架构师
释放数据潜力:利用 MCP 资源让大模型读懂你的服务器
MCP(Model Control Protocol)资源系统是将服务器数据暴露给客户端的核心机制,支持文本和二进制两种类型资源。资源通过唯一URI标识,客户端可通过资源列表或模板发现资源,并使用`resources/read`接口读取内容。MCP还支持资源实时更新通知及订阅机制,确保动态数据的及时性。实现时需遵循最佳实践,如清晰命名、设置MIME类型和缓存策略,同时注重安全性,包括访问控制、路径清理和速率限制等。提供的示例代码展示了如何用JavaScript和Python实现资源支持。
222 80
|
1月前
|
JavaScript 数据可视化 Docker
简易制作MCP服务器并测试
本文介绍了如何简易制作并测试MCP服务器,包括环境搭建、代码实现及Docker部署。首先通过uv包创建项目,在main.py中定义MCP服务器及其工具和资源函数。接着详细说明了在Windows上安装uv、配置Docker镜像加速、生成requirements.txt文件以及编写Dockerfile的过程。最后,通过构建和运行Docker容器部署MCP服务器,并使用Node.js工具测试其功能,确保服务器正常工作。此教程适合初学者快速上手MCP服务器的开发与部署。
665 63
|
1月前
|
存储 人工智能 项目管理
2025年GitHub平台上的十大开源MCP服务器汇总分析
本文深入解析了GitHub上十个代表性MCP(Model Context Protocol)服务器项目,探讨其在连接AI与现实世界中的关键作用。这些服务器实现了AI模型与应用程序、数据库、云存储、项目管理等工具的无缝交互,扩展了AI的应用边界。文中涵盖Airbnb、Supabase、AWS-S3、Kubernetes等领域的MCP实现方案,展示了AI在旅行规划、数据处理、云存储、容器编排等场景中的深度应用。未来,MCP技术将向标准化、安全性及行业定制化方向发展,为AI系统集成提供更强大的支持。
356 2
2025年GitHub平台上的十大开源MCP服务器汇总分析
|
27天前
|
Go API 定位技术
MCP 实战:用 Go 语言开发一个查询 IP 信息的 MCP 服务器
随着 MCP 的快速普及和广泛应用,MCP 服务器也层出不穷。大多数开发者使用的 MCP 服务器开发库是官方提供的 typescript-sdk,而作为 Go 开发者,我们也可以借助优秀的第三方库去开发 MCP 服务器,例如 ThinkInAIXYZ/go-mcp。 本文将详细介绍如何在 Go 语言中使用 go-mcp 库来开发一个查询 IP 信息的 MCP 服务器。
90 0
|
机器学习/深度学习 缓存 Cloud Native
Dubbo-go 源码笔记(二)客户端调用过程
有了上一篇文章《Dubbo-go 源码笔记(一)Server 端开启服务过程》的铺垫,可以类比客户端启动于服务端的启动过程。其中最大的区别是服务端通过 zk 注册服务,发布自己的ivkURL并订阅事件开启监听;而客户应该是通过zk注册组件,拿到需要调用的serviceURL,更新invoker并重写用户的RPCService,从而实现对远程过程调用细节的封装。
24975 0
Dubbo-go 源码笔记(二)客户端调用过程
|
3月前
|
运维 监控 算法
监控局域网其他电脑:Go 语言迪杰斯特拉算法的高效应用
在信息化时代,监控局域网成为网络管理与安全防护的关键需求。本文探讨了迪杰斯特拉(Dijkstra)算法在监控局域网中的应用,通过计算最短路径优化数据传输和故障检测。文中提供了使用Go语言实现的代码例程,展示了如何高效地进行网络监控,确保局域网的稳定运行和数据安全。迪杰斯特拉算法能减少传输延迟和带宽消耗,及时发现并处理网络故障,适用于复杂网络环境下的管理和维护。
|
3月前
|
编译器 Go
揭秘 Go 语言中空结构体的强大用法
Go 语言中的空结构体 `struct{}` 不包含任何字段,不占用内存空间。它在实际编程中有多种典型用法:1) 结合 map 实现集合(set)类型;2) 与 channel 搭配用于信号通知;3) 申请超大容量的 Slice 和 Array 以节省内存;4) 作为接口实现时明确表示不关注值。此外,需要注意的是,空结构体作为字段时可能会因内存对齐原因占用额外空间。建议将空结构体放在外层结构体的第一个字段以优化内存使用。
|
3月前
|
存储 Go
Go 语言入门指南:切片
Golang中的切片(Slice)是基于数组的动态序列,支持变长操作。它由指针、长度和容量三部分组成,底层引用一个连续的数组片段。切片提供灵活的增减元素功能,语法形式为`[]T`,其中T为元素类型。相比固定长度的数组,切片更常用,允许动态调整大小,并且多个切片可以共享同一底层数组。通过内置的`make`函数可创建指定长度和容量的切片。需要注意的是,切片不能直接比较,只能与`nil`比较,且空切片的长度为0。
Go 语言入门指南:切片