52、利用Quartz和OpenGL进行绘图

利用Quartz和OpenGL进行绘图

在iOS开发中,Quartz和OpenGL ES是两种常用的绘图技术。Quartz 2D是一种强大的二维绘图引擎,而OpenGL ES则更侧重于高性能的图形渲染。本文将介绍如何优化Quartz绘图的重绘工作,并通过创建一个名为GLFun的应用程序,展示如何使用OpenGL ES进行绘图。

优化Quartz绘图的重绘工作

在绘图过程中,减少不必要的重绘工作可以显著提高应用程序的性能。通过以下代码,我们可以避免擦除和重绘未受当前拖动影响的视图部分:

_redrawRect = CGRectUnion(_redrawRect,
                          CGRectMake(_lastTouch.x - horizontalOffset,
                                     _lastTouch.y - verticalOffset,
                                     _drawImage.size.width,
                                     _drawImage.size.height));
} else {
    _redrawRect = CGRectUnion(_redrawRect, self.currentRect);
}
_redrawRect = CGRectInset(_redrawRect, -2.0, -2.0);
[self setNeedsDisplayInRect:_redrawRect];

touchesMoved: 方法中,我们也可以进行类似的操作:

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _lastTouch = [touch locationInView:self];
    if (_shapeType == kImageShape) {
        CGFloat horizontalOffset = _drawImage.size.width / 2;
        CGFloat verticalOffset = _drawImage.size.height / 2;
        _redrawRect = CGRectUnion(redrawRect,
                                  CGRectMake(_lastTouch.x - horizontalOffset,
                                             _lastTouch.y - verticalOffset,
                                             _drawImage.size.width,
                                             _drawImage.size.height));
    }
    _redrawRect = CGRectUnion(_redrawRect, self.currentRect);
    [self setNeedsDisplayInRect:_redrawRect];
}

通过这种方式,我们可以减少重绘的工作量,提高应用程序的性能。

GLFun应用程序的搭建

为了展示OpenGL ES的基本用法,我们将重新创建一个使用OpenGL ES的应用程序,名为GLFun。以下是搭建GLFun应用程序的步骤:
1. 复制项目 :关闭QuartzFun Xcode项目,在Finder中复制项目文件夹,并将副本重命名为GLFun。
2. 重命名项目 :双击打开GLFun文件夹,再双击QuartzFun.xcodeproj打开项目。在项目导航器中,将项目名称从QuartzFun改为GLFun。
3. 重命名项目内容 :确认重命名项目内容,Xcode会提示你进行快照备份。
4. 重命名代码组 :将包含所有源代码文件的QuartzFun组重命名为GLFun。
5. 添加文件 :在16 - GLFun文件夹中,找到Texture2D.h、Texture2D.m、OpenGLES2DView.h和OpenGLES2DView.m四个文件,并将它们添加到项目中。
6. 删除Quartz绘图类 :删除BIDQuartzFunView.h和BIDQuartzFunView.m文件。

以上步骤可以用以下流程图表示:

graph TD;
    A[关闭QuartzFun项目] --> B[复制项目文件夹并重命名为GLFun];
    B --> C[打开GLFun文件夹和QuartzFun.xcodeproj];
    C --> D[重命名项目为GLFun];
    D --> E[确认重命名项目内容并备份];
    E --> F[重命名代码组为GLFun];
    F --> G[添加四个文件到项目];
    G --> H[删除BIDQuartzFunView类];
创建BIDGLFunView类

接下来,我们需要创建一个名为BIDGLFunView的类,用于进行OpenGL ES绘图。具体步骤如下:
1. 创建新文件 :在项目中选择GLFun文件夹,按下 ⌘N ,选择Objective-C类,命名为BIDGLFunView,并使其成为OpenGLES2DView的子类。
2. 修改BIDGLFunView.h文件 :将BIDGLFunView.h文件的内容替换为以下代码:

#import "BIDConstants.h"
#import "OpenGLES2DView.h"

@class Texture2D;

@interface BIDGLFunView : OpenGLES2DView
@property CGPoint firstTouch;
@property CGPoint lastTouch;
@property (nonatomic, strong) UIColor *currentColor;
@property BOOL useRandomColor;
@property ShapeType shapeType;
@property (nonatomic, strong) Texture2D *sprite;
@end
  1. 修改BIDGLFunView.m文件 :在BIDGLFunView.m文件中添加以下代码:
#import "BIDGLFunView.h"
#import "UIColor+BIDRandom.h"
#import "Texture2D.h"

@implementation BIDGLFunView

- (id)initWithCoder:(NSCoder*)coder {
    if (self = [super initWithCoder:coder]) {
        _currentColor = [UIColor redColor];
        _useRandomColor = NO;
        _sprite = [[Texture2D alloc] initWithImage:[UIImage
                                                   imageNamed:@"iphone.png"]];
        glBindTexture(GL_TEXTURE_2D, _sprite.name);
    }
    return self;
}

- (void)draw  {
    glLoadIdentity();

    glClearColor(0.78f, 0.78f, 0.78f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    CGColorRef color = _currentColor.CGColor;
    const CGFloat *components = CGColorGetComponents(color);
    CGFloat red = components[0];
    CGFloat green = components[1];
    CGFloat blue = components[2];

    glColor4f(red, green, blue, 1.0);
    switch (_shapeType) {
        case kLineShape: {
            glDisable(GL_TEXTURE_2D);
            GLfloat vertices[4];

            // Convert coordinates
            vertices[0] = _firstTouch.x;
            vertices[1] = self.frame.size.height - _firstTouch.y;
            vertices[2] = _lastTouch.x;
            vertices[3] = self.frame.size.height - _lastTouch.y;
            glLineWidth(2.0);
            glVertexPointer(2, GL_FLOAT, 0, vertices);
            glDrawArrays(GL_LINES, 0, 2);
            break;
        }
        case kRectShape: {
            glDisable(GL_TEXTURE_2D);
            // Calculate bounding rect and store in vertices
            GLfloat vertices[8];
            GLfloat minX = (_firstTouch.x > _lastTouch.x) ?
            _lastTouch.x : _firstTouch.x;
            GLfloat minY = (self.frame.size.height - _firstTouch.y >
                            self.frame.size.height - _lastTouch.y) ?
            self.frame.size.height - _lastTouch.y :
            self.frame.size.height - _firstTouch.y;
            GLfloat maxX = (_firstTouch.x > _lastTouch.x) ?
            _firstTouch.x : _lastTouch.x;
            GLfloat maxY = (self.frame.size.height - _firstTouch.y >
                            self.frame.size.height - _lastTouch.y) ?
            self.frame.size.height - _firstTouch.y :
            self.frame.size.height - _lastTouch.y;

            vertices[0] = maxX;
            vertices[1] = maxY;
            vertices[2] = minX;
            vertices[3] = maxY;
            vertices[4] = minX;
            vertices[5] = minY;
            vertices[6] = maxX;
            vertices[7] = minY;

            glVertexPointer(2, GL_FLOAT , 0, vertices);
            glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
            break;
        }
        case kEllipseShape: {
            glDisable(GL_TEXTURE_2D);
            GLfloat vertices[720];

            GLfloat xradius = fabsf((_firstTouch.x - _lastTouch.x) / 2);
            GLfloat yradius = fabsf((_firstTouch.y - _lastTouch.y) / 2);
            for (int i = 0; i <= 720; i += 2) {
                GLfloat xOffset = (_firstTouch.x > _lastTouch.x) ?
                _lastTouch.x + xradius : _firstTouch.x + xradius;
                GLfloat yOffset = (_firstTouch.y < _lastTouch.y) ?
                self.frame.size.height - _lastTouch.y + yradius :
                self.frame.size.height - _firstTouch.y + yradius;
                vertices[i] = (cos(degreesToRadian(i / 2)) * xradius) + xOffset;
                vertices[i+1] = (sin(degreesToRadian(i / 2)) * yradius) +
                yOffset;
            }

            glVertexPointer(2, GL_FLOAT , 0, vertices);
            glDrawArrays(GL_TRIANGLE_FAN, 0, 360);
            break;
        }
        case kImageShape:
            glEnable(GL_TEXTURE_2D);
            [_sprite drawAtPoint:CGPointMake(_lastTouch.x,
                                            self.frame.size.height - _lastTouch.y)];
            break;
        default:
            break;
    }

    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (_useRandomColor)
        self.currentColor = [UIColor randomColor];

    UITouch* touch = [[event touchesForView:self] anyObject];
    _firstTouch = [touch locationInView:self];
    _lastTouch = [touch locationInView:self];
    [self draw];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];
    _lastTouch = [touch locationInView:self];

    [self draw];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _lastTouch = [touch locationInView:self];

    [self draw];
}
@end
OpenGL ES绘图与Quartz绘图的比较

OpenGL ES和Quartz在绘图方式上有很大的不同。下面以绘制直线和椭圆为例,比较两者的差异。

绘制直线

Quartz绘制直线的代码如下:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, _currentColor.CGColor);
CGContextMoveToPoint(context, _firstTouch.x, _firstTouch.y);
CGContextAddLineToPoint(context, _lastTouch.x, _lastTouch.y);
CGContextStrokePath(context);

OpenGL ES绘制直线的代码如下:

glLoadIdentity();
glClearColor(0.78, 0.78f, 0.78f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
CGColorRef color = currentColor.CGColor;
const CGFloat *components = CGColorGetComponents(color);
CGFloat red = components[0];
CGFloat green = components[1];
CGFloat blue = components[2];
glColor4f(red, green, blue, 1.0);
glDisable(GL_TEXTURE_2D);
GLfloat vertices[4];
vertices[0] = _firstTouch.x;
vertices[1] = self.frame.size.height - _firstTouch.y;
vertices[2] = _lastTouch.x;
vertices[3] = self.frame.size.height - _lastTouch.y;
glLineWidth(2.0);
glVertexPointer (2, GL_FLOAT , 0, vertices);
glDrawArrays (GL_LINES, 0, 2);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

可以看出,OpenGL ES的代码更为复杂,需要更多的步骤来完成相同的任务。

绘制椭圆

Quartz绘制椭圆的代码如下:

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 2.0);
CGContextSetStrokeColorWithColor(context, _currentColor.CGColor);
CGContextSetFillColorWithColor(context, _currentColor.CGColor);
CGRect currentRect;
CGContextAddEllipseInRect(context, self.currentRect);
CGContextDrawPath(context, kCGPathFillStroke);

OpenGL ES绘制椭圆的代码如下:

glLoadIdentity();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDisable(GL_TEXTURE_2D);
CGColorRef color = _currentColor.CGColor;
const CGFloat *components = CGColorGetComponents(color);
CGFloat red = components[0];
CGFloat green = components[1];
CGFloat blue = components[2];
glColor4f(red, green, blue, 1.0);
GLfloat vertices[720];
GLfloat xradius = fabsf((_firstTouch.x - _lastTouch.x)/2);
GLfloat yradius = fabsf((_firstTouch.y - _lastTouch.y)/2);
for (int i = 0; i <= 720; i+=2) {
    GLfloat xOffset = (_firstTouch.x > _lastTouch.x) ?
    _lastTouch.x + xradius : _firstTouch.x + xradius;
    GLfloat yOffset = (_firstTouch.y < _lastTouch.y) ?
    self.frame.size.height - _lastTouch.y + yradius :
    self.frame.size.height - _firstTouch.y + yradius;
    vertices[i] = (cos(degreesToRadian(i / 2))*xradius) + xOffset;
    vertices[i+1] = (sin(degreesToRadian(i / 2))*yradius) +
    yOffset;
}
glVertexPointer (2, GL_FLOAT , 0, vertices);
glDrawArrays (GL_TRIANGLE_FAN, 0, 360);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];

OpenGL ES没有直接绘制椭圆的函数,需要手动计算顶点数组,代码更加复杂。

更新BIDViewController和Nib文件

为了使应用程序正常工作,我们还需要对BIDViewController.m文件和Nib文件进行一些修改。

更新BIDViewController.m文件

将所有对BIDQuartzFunView的引用改为BIDGLFunView。具体修改如下:
- 将 #import "BIDQuartzFunView.h" 替换为 #import "BIDGLFunView.h"
- 在 changeColor: 方法中,将 BIDQuartzFunView *funView = (BIDQuartzFunView *)self.view; 替换为 BIDGLFunView *funView = (BIDGLFunView *)self.view;
- 在 changeShape: 方法中,将 [(BIDQuartzFunView *)self.view setShapeType:[control selectedSegmentIndex]]; 替换为 [(BIDGLFunView *)self.view setShapeType:[control selectedSegmentIndex]];

更新Nib文件

将Nib文件中的视图类从BIDQuartzFunView改为BIDGLFunView。具体步骤如下:
1. 打开BIDViewController.xib文件。
2. 在Dock中选择Quartz Fun View,按下 ⌥⌘3 打开身份检查器。
3. 将类从BIDQuartzFunView改为BIDGLFunView。

通过以上步骤,我们完成了GLFun应用程序的创建,并展示了OpenGL ES和Quartz在绘图方面的差异。虽然OpenGL ES更为强大,但使用起来也更加复杂。在实际开发中,我们需要根据具体需求选择合适的绘图技术。

利用Quartz和OpenGL进行绘图(续)

总结OpenGL ES绘图流程

OpenGL ES的绘图过程可以总结为以下三个步骤:
1. 在上下文绘制 :设置绘图的各种参数,如颜色、顶点数组等,进行具体的图形绘制操作。
2. 渲染上下文到缓冲区 :将绘制的内容渲染到缓冲区中。
3. 呈现渲染缓冲区 :将缓冲区中的内容显示到屏幕上。

以下是一个简单的表格总结OpenGL ES绘制直线的流程:
| 步骤 | 操作 | 代码示例 |
| — | — | — |
| 1. 绘制准备 | 重置变换、清除背景、设置颜色、禁用纹理 | glLoadIdentity(); glClearColor(0.78, 0.78f, 0.78f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); CGColorRef color = currentColor.CGColor; const CGFloat *components = CGColorGetComponents(color); CGFloat red = components[0]; CGFloat green = components[1]; CGFloat blue = components[2]; glColor4f(red, green, blue, 1.0); glDisable(GL_TEXTURE_2D); |
| 2. 定义顶点数组 | 将CGPoint转换为顶点数组 | GLfloat vertices[4]; vertices[0] = _firstTouch.x; vertices[1] = self.frame.size.height - _firstTouch.y; vertices[2] = _lastTouch.x; vertices[3] = self.frame.size.height - _lastTouch.y; |
| 3. 绘制图形 | 设置线宽、传递顶点数组、绘制图形 | glLineWidth(2.0); glVertexPointer (2, GL_FLOAT , 0, vertices); glDrawArrays (GL_LINES, 0, 2); |
| 4. 渲染和呈现 | 绑定渲染缓冲区、呈现渲染缓冲区 | glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; |

不同图形绘制的复杂度分析

从前面绘制直线和椭圆的代码可以看出,Quartz和OpenGL ES在不同图形绘制上的复杂度差异明显。以下是一个对比表格:
| 图形类型 | Quartz复杂度 | OpenGL ES复杂度 | 原因 |
| — | — | — | — |
| 直线 | 低 | 高 | Quartz有简单的API直接绘制直线,而OpenGL ES需要更多步骤设置和传递顶点数组 |
| 椭圆 | 低 | 高 | Quartz有直接绘制椭圆的API,OpenGL ES没有,需要手动计算大量顶点数组 |
| 矩形 | 低 | 中 | 两者都需要计算顶点,但OpenGL ES需要更多的设置和渲染步骤 |
| 图像 | 低 | 低 | 有Texture2D类的帮助,OpenGL ES绘制图像相对简单 |

触摸事件处理差异

在处理触摸事件时,Quartz和OpenGL ES的处理方式也有所不同。Quartz通常是告诉视图需要重新显示,而OpenGL ES则直接调用 draw 方法。以下是两者触摸事件处理的代码对比:

Quartz触摸事件处理
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 处理触摸开始事件
    [self setNeedsDisplay];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    // 处理触摸移动事件
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // 处理触摸结束事件
    [self setNeedsDisplay];
}
OpenGL ES触摸事件处理
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (_useRandomColor)
        self.currentColor = [UIColor randomColor];

    UITouch* touch = [[event touchesForView:self] anyObject];
    _firstTouch = [touch locationInView:self];
    _lastTouch = [touch locationInView:self];
    [self draw];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _lastTouch = [touch locationInView:self];
    [self draw];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _lastTouch = [touch locationInView:self];
    [self draw];
}

可以看出,OpenGL ES不需要手动指定需要更新的屏幕部分,它会自动利用硬件加速以最高效的方式进行绘制。

选择合适的绘图技术

在实际开发中,选择Quartz还是OpenGL ES进行绘图需要根据具体需求来决定。以下是一些选择的建议:
- 简单图形绘制和快速开发 :如果应用程序只需要绘制简单的二维图形,如直线、矩形、椭圆等,并且对性能要求不是特别高,Quartz是一个不错的选择。它的API简单易用,开发效率高。
- 高性能图形渲染和复杂场景 :如果应用程序需要处理大量的图形、动画,或者需要进行三维图形的渲染,OpenGL ES更适合。它可以利用硬件加速,提供更高的性能,但开发难度也相对较大。

以下是一个简单的决策流程图:

graph TD;
    A[需求分析] --> B{图形复杂度};
    B -->|简单二维图形| C{性能要求};
    C -->|低| D[选择Quartz];
    C -->|高| E{是否有大量图形或动画};
    E -->|否| D;
    E -->|是| F[选择OpenGL ES];
    B -->|复杂图形或三维图形| F;

通过以上对Quartz和OpenGL ES绘图技术的详细分析,我们可以更好地理解它们的特点和差异,在实际开发中做出更合适的选择,以满足不同应用程序的需求。

代码下载地址: https://pan.quark.cn/s/a4b39357ea24 图书馆系统非常适合运用C++面向对象的特性进行建模。图书馆管理系统主要由四个关键模块构成:图书借阅、图书归还、图书维护以及读者服务。在系统设计中,可以定义一个读者类(Reader),用于存储每位读者的详细资料;读者数据库类(Rdatabase),用于管理所有读者的信息;图书类(Book),用于记录每本图书的基本属性;图书数据库类(Bdatabase),用于维护所有图书的记录。 【图书馆管理系统构建】 基于C++面向对象编程的图书馆管理系统,其核心功能划分为四个主要部分:图书借阅、图书归还、图书维护读者服务。该系统通过设计多种类来模拟图书馆的实际运作,包括读者类(Reader)、读者数据库类(Rdatabase)、图书类(Book)以及图书数据库类(Bdatabase)。 1. **读者类(Reader)**: - 该类包含读者的基础资料,例如删除标记(tag)、读者编号(no)、姓名(name)以及所借图书列表(borbook)。 - 通过构造函数对读者信息进行初始化。 - 拷贝构造函数用于复制读者的姓名信息。 - 提供一系列成员函数,以支持信息的获取设置操作。 2. **读者数据库类(Rdatabase)**: - 包含一个读者记录数组(read),并使用记录指针(top)来标识最新添加的读者信息。 - 构造函数从read.txt文件中加载所有读者数据,并在析构函数中将未删除的记录保存回文件。 - 提供管理读者信息的接口,例如添加、删除查找功能。 3. **图书类(Book)**: - 该类存储图书的基本属性,包括删除标记、图书编号、书名(name)以及图书的在架状态...
内容概要:本文围绕综合能源系统与模型预测控制(MPC)的滚动优化展开深入研究,重点阐述了基于Matlab的MPC方法在综合能源系统优化调度中的建模、仿真与求解过程。内容涵盖MPC的核心原理、滚动优化机制及其在多能协同系统中的实际应用,结合多个典型案例展示其在微电网调度、风光储协调、电动汽车接入、氢能系统等前沿方向的具体实现路径。文档配套提供了丰富的Matlab/Simulink代码与仿真模型,涵盖从基础算法构建到高水平论文复现的全过程,助力科研人员快速掌握先进控制策略的技术细节与工程实现方法。同时,资源汇总了大量相关研究主题与可复现课题,形成完整的科研支持体系。; 适合人群:具备电力系统、自动化或控制理论背景,熟悉Matlab编程,从事能源系统优化、智能控制、微电网调度及相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①系统学习并掌握MPC在综合能源系统中的滚动优化建模与实现方法;②高效复现已发表高水平期刊论文中的算法与仿真模型;③支撑新能源接入、多能协同调度、需求响应等方向的科研项目申报、实验验证与学术论文撰写。; 阅读建议:此资源以科研复现为导向,强调理论与代码实践深度融合,建议读者结合所提供的Matlab代码与Simulink模型进行动手操作,重点关注MPC控制器设计、约束处理机制与多目标优化策略的实现细节,并通过对比不同场景拓展算法应用边界,提升科研创新能力。
内容概要:本文针对考虑需求响应的微电网优化调度问题,提出了一种基于改进多目标灰狼算法(GWO)的优化方法,并通过Matlab代码实现了完整的仿真验证。研究在传统灰狼算法基础上引入改进机制,有效提升了算法的收敛速度、全局搜索能力Pareto前沿分布质量,用于求解包含经济运行成本、碳排放水平、可再生能源利用率等多重目标的微电网调度模型。模型充分融合用户侧需求响应机制,利用分时电价等激励手段引导负荷转移与削峰填谷,从而增强系统对光伏、风电等间歇性能源的消纳能力,降低综合运行成本与环境影响。文中系统阐述了多目标优化建模过程、算法改进策略、约束处理方法及仿真结果对比分析,验证了该方法在获取高质量非劣解集辅助决策方面的优越性。; 适合人群:适用于电力系统、能源互联网、自动化控制、智能优化算法等相关领域的硕士/博士研究生、科研人员,以及从事微电网能量管理、综合能源系统优化、低碳调度等工作的工程技术人员。; 使用场景及目标:①应用于微电网能量管理系统(EMS)中实现多目标协同优化调度;②为基于电价激励的需求响应项目提供负荷调控策略与量化分析工具;③作为智能计算算法在能源系统优化中应用的教学案例与科研参考,支持进一步拓展至多能互补、多微网互联等复杂场景的研究。; 阅读建议:建议读者结合提供的Matlab代码深入理解算法实现细节,重点关注目标函数构造、约束条件处理、多目标适应度评估及决策者偏好选择机制;可尝试将该框架迁移至含氢能储能、电动汽车集群等新型设备的综合能源系统中进行性能测试与算法改进。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值