游戏编程模式-享元模式(Flyweight)

深入理解设计模式:享元模式(Flyweight Pattern) 在软件开发中,我们经常会遇到需要创建大量相似对象的情况。如果每个对象都独立存储所有数据,将会消耗大量内存资源,导致系统性能下降。享元模式(Flyweight Pattern)正是为解决这一问题而生的经典设计模式。本文将深入探讨享元模式的核心概念、实现原理、应用场景以及实际案例,帮助读者全面理解并掌握这一高效的对象共享技术。 阅读详情

享元模式(Flyweight)

迷雾散去,显露雄伟的古老森林;阳光洒落,尽显各式的繁枝绿叶。

这是我们游戏开发者梦想的超凡场景,这样的场景通常由一个模式支撑着,它的名字低调至极:享元模式。

森林

我可以用几句话描述一片森林,但是在游戏中实现却是另外一回事。当你看到一整片树林填满屏幕时,图像编程人员所知道的是他们必须每1/60秒就要将数百万个面(多边形)发送给GPU。

我们所说的成千上万的树,一颗树就要数千个面(多边形)去描述。即使有足够多的内存存储这片森林,为了渲染它,数据也必须从CPU传送到GPU,这会造成CPU到GPU的带宽总线紧张(受限)。

那么描述一棵树大概需要哪些元素呢?

  • 描述树干、树枝和绿叶形状的多边形网格
  • 树皮和树叶的纹理。
  • 在森林中的位置和朝向
  • 使得每棵树有所不同的可调参数:大小/色彩等

如果在代码中描述一棵树,那么大概会是如下所示:

class Tree
{
private:
  Mesh mesh_;
  Texture bark_;
  Texture leaves_;
  Vector position_;
  double height_;
  double thickness_;
  Color barkTint_;
  Color leafTint_;
};

从上代码可知,一个Tree对象含有很多数据,其中的网格和纹理数据量尤其大。在游戏更新的一帧时间内是不可能把一片森林的树的对象全部发送给GPU的。幸运的是享元模式可以解决这个问题。

关键就是即使森林中有成千的树,这些树是相似的。树使用了相同的网格和纹理。即不同实例化后的Tree对象中mesh_/bark_/leaves_是一样的。因此,我们可以把这些共享的元素剥离出来,形成下述的分离的两个类:

// 共享的类
class TreeModel
{
private:
  Mesh mesh_;
  Texture bark_;
  Texture leaves_;
};

// 细分的类
class Tree
{
private:
  TreeModel* model_; // 指向共享的对象

  Vector position_;
  double height_;
  double thickness_;
  Color barkTint_;
  Color leafTint_;
};

地形

上面提到了森林中的树木,那么游戏中必然涉及到了树木所位于的地形。为了加深对这个模式的理解,我们继续讨论游戏中的地形问题。

游戏中的地形有各式各样:草地、沙地、丘陵、河流等。不同地形在游戏中可能有以下主要的属性:

  • 人物角色在该地的移速
  • 人物是否可以行走的标识
  • 用于渲染的纹理

那么我们自然可以想到如何优雅地表示地形:

class Terrain
{
public:
  Terrain(int movementCost,
          bool isAccess,
          Texture texture)
  : movementCost_(movementCost),
    isAccess_(isAccess),
    texture_(texture)
  {}

  int getMovementCost() const { return movementCost_; }
  bool isAccess() const { return isAccess_; }
  const Texture& getTexture() const { return texture_; }

private:
  int movementCost_;
  bool isAccess_;
  Texture texture_;
};

自然地,那么整个游戏世界上的地形该如何表达呢?最直接简单的如下所示:

class World
{
    void setTerrain(int x, int y, Terrain dst) {
        tiles_[x][y] = dst;
    }
private:
  Terrain tiles_[WIDTH][HEIGHT];

  // Other stuff...
};

那么上述的地形如何使用享元模式呢?请看下述的例子:

World wd;
wd.setTerrain(0, 0, Terrain(1, true, GRASS_TEXTURE)); // 草地
wd.setTerrain(0, 1, Terrain(1, true, GRASS_TEXTURE)); // 草地

wd.setTerrain(1, 0, Terrain(3, true, HILL_TEXTURE)); // 丘陵
wd.setTerrain(1, 1, Terrain(3, true, HILL_TEXTURE)); // 丘陵

wd.setTerrain(2, 0, Terrain(1, false, RIVER_TEXTURE)); // 河流
wd.setTerrain(2, 1, Terrain(1, false, RIVER_TEXTURE)); // 河流

这个例子中,[0,0]和[0,1]位置上的都是使用了相同的草地地形,Terrain实例元素完全一致,跟外部的位置因素没有任何关系,因此这个Terrain元素可以分离出来的元素。World实现变为如下所示:

class World
{
    void setTerrain(int x, int y, Terrain* dst) {
        tiles_[x][y] = dst;
    }
private:
  Terrain* tiles_[WIDTH][HEIGHT];

  // Other stuff...
};

最终的地形赋值如下所示:

World wd;
Terrain grassTerrain = Terrain(1, true, GRASS_TEXTURE);
Terrain hillTerrain = Terrain(3, true, HILL_TEXTURE);
Terrain riverTerrain = Terrain(1, false, RIVER_TEXTURE);
wd.setTerrain(0, 0, &grassTerrain); // 草地
wd.setTerrain(0, 1, &grassTerrain); // 草地

wd.setTerrain(1, 0, &hillTerrain); // 丘陵
wd.setTerrain(1, 1, &hillTerrain); // 丘陵

wd.setTerrain(2, 0, &riverTerrain); // 河流
wd.setTerrain(2, 1, &riverTerrain); // 河流

享元模式(关键概念)

享元模式(Flyweight Pattern)是一种结构型设计模式,用于通过共享尽可能多的对象,以减少内存占用和提高性能。
它将对象的状态分为内部状态与外部状态,从而实现对可共享部分的复用。

概念说明示例
Flyweight(享元对象)表示可以共享的对象,封装内部状态。共享的网格、纹理、地形数据
Intrinsic State(内部状态)对象中可被多个实例共享的部分,不随环境变化。同上
Extrinsic State(外部状态)每次使用享元时由外部传入的部分,不可共享。树的位置、颜色、朝向、地形位置
Flyweight Factory(享元工厂)创建并管理享元对象的共享池,确保重复使用已有实例。MeshManager、MaterialCache
Client(客户端)使用享元对象的代码部分,负责维护外部状态。场景渲染器、Entity系统

享元模型在渲染中的作用(OpenGL实践)

上述的两个例子用了享元模式共享了很多数据,目前我们只看到了能够减少内存的作用,那么再来看下该模式是如何提升渲染过程的性能的?

我们以OpenGL为例,OpenGL天然支持享元模式。很多时候渲染的时候可以用共享的顶点数据,在屏幕上进行不同位置的渲染。如下所示


#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <vector>
#include <cmath>
#include <cstdlib>

// 顶点着色器
const char* vertexShaderSrc = R"(
#version 330 core
layout(location = 0) in vec2 aPos;     // 顶点坐标(共享)
layout(location = 1) in vec2 iOffset;  // 实例位置
layout(location = 2) in float iScale;  // 实例缩放
layout(location = 3) in vec3 iColor;   // 实例颜色

out vec3 vColor;

void main()
{
    vec2 pos = aPos * iScale + iOffset;
    gl_Position = vec4(pos, 0.0, 1.0);
    vColor = iColor;
}
)";

// 片段着色器
const char* fragmentShaderSrc = R"(
#version 330 core
in vec3 vColor;
out vec4 FragColor;

void main()
{
    FragColor = vec4(vColor, 1.0);
}
)";

// Shader编译工具函数
GLuint createShader(GLenum type, const char* src)
{
    GLuint shader = glCreateShader(type);
    glShaderSource(shader, 1, &src, nullptr);
    glCompileShader(shader);

    GLint success;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        char log[512];
        glGetShaderInfoLog(shader, 512, nullptr, log);
        std::cerr << "Shader compile error:\n" << log << std::endl;
    }
    return shader;
}


int main()
{
    // 初始化GLFW
    if (!glfwInit()) return -1;
    GLFWwindow* window = glfwCreateWindow(800, 600, "Two-leaf Grass (Flyweight)", nullptr, nullptr);
    if (!window) { glfwTerminate(); return -1; }
    glfwMakeContextCurrent(window);

    // 初始化GLEW
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK)
    {
        std::cerr << "Failed to init GLEW\n";
        return -1;
    }

    // -------------------------------
    // 共享几何(每棵草由两片叶子组成)
    // -------------------------------
    float vertices[] = {
        // 第一片叶子(往左)
        -0.02f, 0.0f,
         0.00f, 0.1f,
         0.02f, 0.0f,
         // 第二片叶子(往右)
          0.00f, 0.0f,
          0.02f, 0.1f,
          0.04f, 0.0f
    };

    GLuint vao, vbo;
    glGenVertexArrays(1, &vao);
    glGenBuffers(1, &vbo);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // -------------------------------
    // 实例数据(位置 + 缩放 + 颜色)
    // -------------------------------
    const int NUM = 150;
    std::vector<float> instanceData;
    for (int i = 0; i < NUM; ++i)
    {
        float x = ((rand() % 200) / 100.0f - 1.0f); // [-1, 1]
        float y = ((rand() % 100) / 100.0f - 1.0f); // [-1, 0]
        float scale = 0.4f * ((rand() % 100) / 100.0f + 0.5f);
        float r = 0.1f + (rand() % 30) / 255.0f;
        float g = 0.6f + (rand() % 80) / 255.0f;
        float b = 0.1f + (rand() % 30) / 255.0f;
        instanceData.insert(instanceData.end(), { x, y, scale, r, g, b });
    }

    GLuint instanceVBO;
    glGenBuffers(1, &instanceVBO);
    glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
    glBufferData(GL_ARRAY_BUFFER, instanceData.size() * sizeof(float), instanceData.data(), GL_STATIC_DRAW);

    // 实例属性
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribDivisor(1, 1);

    glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(2 * sizeof(float)));
    glEnableVertexAttribArray(2);
    glVertexAttribDivisor(2, 1);

    glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(3);
    glVertexAttribDivisor(3, 1);

    // 着色器程序
    GLuint vs = createShader(GL_VERTEX_SHADER, vertexShaderSrc);
    GLuint fs = createShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
    GLuint shader = glCreateProgram();
    glAttachShader(shader, vs);
    glAttachShader(shader, fs);
    glLinkProgram(shader);
    glUseProgram(shader);

    glClearColor(0.3f, 0.6f, 0.9f, 1.0f);

    // -------------------------------
    // 渲染循环
    // -------------------------------
    while (!glfwWindowShouldClose(window))
    {
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shader);
        glBindVertexArray(vao);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, NUM);
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // 清理
    glDeleteVertexArrays(1, &vao);
    glDeleteBuffers(1, &vbo);
    glDeleteBuffers(1, &instanceVBO);
    glfwTerminate();
    return 0;
}

解析代码:

  • vao: 使用了一份共同的vertices,代表叶子几何形状(两个三角形)
  • instanceVBO: 表示每个实例的数据(位置 + 缩放 + 颜色)
  • glDrawArraysInstanced:执行对实例的绘制

在这里插入图片描述

[设计模式](八):组合模式(Composite)与享元模式(Flyweight)详解 组合模式(Composite)与享元模式(Flyweight) 阅读详情

相关推荐

C++ 享元模式

享元模式(Flyweight Pattern)运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。

青春不老,奋斗不止! 9452

《游戏编程模式》学习笔记(三)享元模式 Flyweight Pattern

为了减少需要推送到GPU的数据量,我们想把共享的数据——TreeModel——只发送一次。然后,我们分别发送每个树独特的数据——位置,颜色,大小。第一部分是一块需要渲染多次的共同数据——在例子中是树的网格和纹理。我们只需要一个TreeModel实例化的对象就好了,剩下的树只需要保存一个TreeModel的指针,就可以很快的找到这个对象。这就是享元模式,实际上我认为享元模式没有什么好说的东西,所以这里的大部分内容我都是直接照搬原著的。在系统设计中,对象实例进行分类后,发现真正有区别的分类很少的时候。

Currybeefer的博客 761

几何非线性-屈曲分析

结构系统的破坏分为两种,一类是因材料软化而引起的强度破坏,另一类是几何软化使得结构的几何形状发生极大变化,从而使结构丧失承载能力。在材料力学中是这样定义压杆失稳的:中心受压直杆(理想模型)在直线状态下平衡,由稳定平衡转化为不稳定平衡时所受的轴向压力的界限值,称为临界压力。中心受压直杆在临界压力的作用下,其直线形态的平衡开始丧失稳定性,简称为失稳。显然,这个定义只使用于理想的压杆模型,那么关于失稳和屈曲是否存在一般性定义呢?

YORU_NO_KUNI的博客 1万+

GoF设计模式(十二) - 享元模式

前言 享元x模式(Flyweight Pattern)运用共享技术有效地支持大量细粒度的对象,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。 模式所涉及的角色 Flyweight: 享元接口,通过这个接口传入外部状态并作用于外部状态; ConcreteFlyweight: 具体的享元实现对象,必须是可共享的,需要封装享元对象的内部状态; UnsharedConcreteFlyweight: 非共享的享元实现对象,并不是.

nbme的博客 596

新手避坑指南:用SARScape 5.6.2处理Sentinel-1数据做SBAS-InSAR,这些参数设置错了白忙活

本文详细介绍了使用SARScape 5.6.2处理Sentinel-1数据做SBAS-InSAR时的关键参数设置技巧,帮助新手避免常见错误。重点解析了连接图生成、干涉工作流、DEM处理等环节的12个关键参数,包括Max Normal Baseline、Unwrapping Coherence Threshold等,提供实测安全值范围和优化建议,确保处理效率和结果准确性。

weixin_30694389的博客 557

在macOS上使用GitHub的超详细指南

GitHub:免费的远程仓库,可用来托管自己的开源项目。它利用了Git强大的克隆和分支功能,让我们参与别人的开源项目和让别人参与自己的开源项目变得简单高效。

Mars的博客 3391

Game Programming Patterns-再探设计模式

游戏编程模式- 再探设计模式,本章目录。

AutoVision (by 仙道菜) 2085

无感无刷电机三段式启动避坑指南:从预定位到闭环切换的STM32代码实现

本文深入解析无感无刷电机的三段式启动流程,涵盖预定位、启动加速与闭环切换三大阶段。重点对比了恒频升压、恒压升频与升频升压三种核心启动策略的优劣,并提供了基于STM32的详细状态机代码实现与关键参数调试指南,帮助工程师有效规避启动失步、线圈过热等常见问题,实现稳定可靠的无感驱动。

herb5的博客 535

软件设计模式--第三章 结构型模式--享元模式

软件设计模式 结构型模式 享元模式

ljn1046016768的博客 424

VSCode插件全攻略:5分钟搞定Mermaid流程图实时预览(含常见报错解决)

本文提供了一份详细的VSCode插件实战指南,帮助开发者快速配置Mermaid流程图实时预览环境。文章涵盖核心插件选择、高效工作流搭建,并重点解决了图表渲染失败、样式异常等常见报错问题,旨在提升技术文档的编写效率与可视化质量。

rust6ferris的博客 1486

【Codecs系列】HEVC-SCC(一):核心技术介绍

DATE: 2020.6.4【Tags: HEVC SCC】 1、参考网址 2、SCC简介 3、HEVC-SCC核心技术

优快云明星博主,认证博客专家,视频、Matlab领域优质创作者。 4803

设计模式(14) -结构型模式 之 享元模式

文章目录1. 享元模式概述1.1 定义1.2 使用场景1.3 核心概念2. 代码2.1 场景介绍2.2 分析2.3 类图2.3.1 百度上的类图2.3.2 本例类图2.4 代码3. 优缺点与典型场景3.1 优点3.2 缺点3.3 典型应用3. 总结 1. 享元模式概述 文章例子来自一个b站的视频。 1.1 定义 享元模式(FlyWeight),运用共享技术有效的支持大量细粒度的对象 --------------------- 来自百度百科 1.2 使用场景 系统中有大量对象,这些对象消耗大量内存,

immort.L 的博客 636

YOLO26改进 - C2PSA | C2PSA融合DiffAttention差分注意力:轻量级差分计算实现高效特征降噪,提升模型抗干扰能力

本文介绍了 DiffCLIP,一种将差分注意力机制集成到 CLIP 架构的视觉 - 语言模型,并将其应用于 YOLO26。差分注意力机制通过计算两个互补注意力分布的差值,抵消无关信息干扰。单头差分注意力将 Q 和 K 拆分,分别计算注意力分布后做差值融合;多头差分注意力则每个头独立执行差分操作后聚合输出。关键参数 λ 可动态调控噪声抵消强度。我们将 DiffAttention 代码集成到 YOLO26 中,替换原有模块。实验表明,改进后的 YOLO26 在图像 - 文本理解任务中表现出色。

魔改工程师的博客 601

享元模式

享元模式 标签 : Java与设计模式 内存属于稀缺资源, 不能随便浪费. 如果有很多相同/相似的对象, 我们可以通过享元节省内存. 内部状态 vs. 外部状态 享元模式(Flyweight): 运用共享技术有效地重用大量...

weixin_34160277的博客 79

C++实现设计ATM机系统

这个示例实现了一个基本的ATM机系统,支持注册、登录、存款、取款、查询余额和保存数据等功能。当然,这只是一个简单的ATM机模拟,实际的ATM机系统需要更复杂的逻辑和更严格的安全措施。C++编写一个简单的ATM机系统,我将提供一个基本的实现示例,但由于ATM机系统的复杂性和安全性要求,这只是一个简单的模拟,不适合在实际环境中使用。试设计一简单模拟ATM机系统,系统以菜单方式工作,采用自定义函数设计各功能模块被主菜单调用,使之能提供以下基本选项。=========欢迎使用ATM=========

走向CTO的路上... 1243

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

23种设计模式——享元模式(Flyweight Pattern)

本文介绍了23种经典设计模式之——享元模式(Flyweight Pattern)的使用及说明

Meteors.的博客 1345

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

设计模式:享元模式(Flyweight Pattern)

享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。

TechNomad的博客 852

设计模式系列-享元模式-Flyweight

为了更好的理解享元模式,我们需要理解两个概念: - 固有数据:存储在享元对象里,和其他对象共享 - 非固有数据:不能共享的数据,存在客户度对象里,用来被计算或者传入享元对象

独 coding 乐不如众 coding 乐 181

【设计模式】享元模式(Flyweight Pattern)

  ???? 核心 享元模式通过共享多个对象所共有的相同状态,减少对象占用的内存。 享元模式的本质其实是缓存。   ???? 问题场景 你是一名游戏地图建模师,复杂搭建游戏地图。 现在,你需要搭建一座森林。森林中有成千上万个棵 树(Tree) ,没棵树具有的属性有 位置(x, y) 、高度(h) 、颜色(color) 、材质(texture) 。你做出了成千上万棵树的对象,搭建出了一座美丽而壮观的森林。 你迫不及待的把地图工程发送给你的朋友,让他体验一下这座森林。尽管地图工程在你的电脑上

丧心病狂Loli控的博客 281

C# 设计模式:第十一章 - 结构型模式 之 享元模式(Flyweight Pattern)

在这章中,我们学习了享元模式的基本概念、应用场景、实现方法以及注意事项。享元模式通过共享相同的数据来减少内存的使用,特别适用于需要创建大量相似对象的场景,如文本编辑器、游戏开发等。它不仅可以减少内存的消耗,还可以提高系统的性能。我们通过两个实际的代码示例展示了如何在 C# 中实现享元模式,包括基本的享元模式和文本编辑器中的字符格式应用。最后,我们还讨论了一些使用享元模式时需要注意的地方,帮助你在实际开发中做出明智的选择。

caifox的博客 1313

设计模式之 Flyweight Mode 享元模式:Swift 实现

把“重量级”但又不是个性化(其它类可能也有一模一样的,所以会引起大量重复)的属性交给公共的类去存放,可以减轻原来类的负担。

iOS 开发 592

设计模式之享元模式【结构型模式】

一、概述二、结构三、案例实现四、优缺点和使用场景五、JDK源码解析​ 运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。以上就是设计模式之享元模式【结构型模式】的相关知识点,希望对你有所帮助。积跬步以至千里,积怠惰以至深渊。时代在这跟着你一起努力哦!

哥的时代,为你保驾护航 1624

编程自学指南:java程序设计开发,Java 享元模式(Flyweight),享元模式的概念和核心思想,使用享元模式优化系统的内存使用,享元模式的结构和角色

编程自学指南:java程序设计开发,Java 享元模式(Flyweight),享元模式的概念和核心思想,使用享元模式优化系统的内存使用,享元模式的结构和角色

zl515035644的博客 763

php实现享元模式,php Flyweight享元模式使用例子

星际的战斗达到后面,地图里面的部队很多,如果我们把每个兵的图像动画和属性值作为一个对象的话,系统的内存里会消耗极大我们在玩的时候会发现,因为星际里面的种族只有三个,其实兵种只有几十个。虽然每个独立的士兵剩余的血不同,但是同一兵种的图像动画是一样的,即使不同的玩家,只是不同的颜色。比如每个人族的机枪兵。而且大多数玩家只用到常用的一些兵种,很多时候不会制造所有的兵种。待解决的问题:把把兵种的图像动画共...

weixin_39844963的博客 124

Python 享元模式:高效利用内存的设计模式

Python 中的享元模式是一种有效的结构型设计模式,它通过共享对象的内部状态来减少内存使用量,提高系统性能。通过理解享元工厂、享元对象以及内部状态和外部状态这些关键要点,我们可以根据不同的应用场景实现享元模式。在图形绘制系统、数据库连接池、游戏开发中的角色系统等场景中,享元模式都发挥着重要的作用。与单例模式和对象池模式的比较有助于我们更深入地理解享元模式的特点和优势,从而在不同的编程场景中准确地选择合适的设计模式,提高软件的可维护性和可扩展性。

liuhailong0511的博客 1228
上一篇: 游戏编程模式-命令模式(Command)
下一篇: C/C++程序员如何理解Godot的场景式游戏开发逻辑的?
慷仔
博客等级 码龄9年 88粉丝 46原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值