OpenAtom OpenHarmony(以下简称“OpenHarmony”)提供了Image组件支持GIF动图的播放,但是缺乏扩展能力,不支持播放控制等。今天介绍一款三方库——ohos-gif-drawable三方组件,带大家一起玩转GIF的数据渲染,搞定GIF动图的各种需求。

效果演示

本文将从5个小节来带领大家使用ohos-gif-drawable这一款三方库,其中1、2、3这3个小节,主要介绍了ohos-gif-drawable的核心能力、GIF软解码和GIF绘制。4和5小节主要是扩展讨论,如何添加滤镜效果和软解码遇到的耗时问题。

1.GIF的文件格式理论基础

工欲善其事必先利其器。首先我们需要为自己打下理论基础。了解GIF的数据格式,为后续解码GIF提供理论支持。

通过学习GIF的文件格式,我们对于GIF的组成格式有了一定的了解,并且有助于理解后面GIF的解码。

在开始介绍之前,我想让大家了解一下整体的结构思路如下图:

其中gifuct-js三方库主要完成了解码的工作。

ohos-gif-drawable三方库则是在gifuct-js的三方库之上,进行了封装。并结合了OpenHarmony的Canvas绘制能力,达到了播放和控制GIF的能力。

2.GIF软解码:gifuct-js三方库介绍

GIF解码我们使用了gifuct-js这个库,它是一个纯JavaScript的GIF解码库。首先我们需要了解基础用法。

2.1 参考样例将一个文件ArrayBuffer转换为GIF解码后的帧数据数组。

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, true)

  

2.2 由于OpenHarmony的Image生成PixelMap需要的数据是BGRA数据,而2.1生成的frames所有数组中的patch字段则是RGBA数据,所以我们需要使用

//javascript
var gif = parseGIF(arraybuffer)
var frames = decompressFrames(gif, false)

  

然后将frame目前还未生成的patch字段数据,通过generatePatch 函数,将RGBA的数据更换为BGRA即可,如下代码所示:

//javascript
const generatePatch = image => {
const totalPixels = image.pixels.length
const patchData = new Uint8ClampedArray(totalPixels * 4)
for (var i = 0; i < totalPixels; i++) {
const pos = i * 4
const colorIndex = image.pixels[i]
const color = image.colorTable[colorIndex] || [0, 0, 0]
patchData[pos] = color[2] // B
patchData[pos + 1] = color[1]// G
patchData[pos + 2] = color[0] // R
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0//A
}
return patchData
}

  

generatePatch函数,在这里会根据颜色表colorTable和基于颜色表的图像数据pixels以及透明度transparentIndex生成BGRA格式的patchData,这个数据和Canvas中getImageData获取的ImageData数据是一致的,都是Uint8ClampedArray类型,可以直接使用putImageData让canvas绘制。

最后,生成的patchData赋值给Frame的patch字段。

这里我们并没有直接使用Canvas的putImageData直接绘制。为了提升扩展性,我们使用了Image的能力来生成PixelMap,这样处理为后续滤镜效果提供了可能,也方便后续绘制流程。

好了,到这里我们就基本上把gifuct-js库的基础使用简单介绍完了。

如何使用GIF:ohos-gif-drawable三方库的介绍。

我们先来看看整个ohos-gif-drawable组件的模型图,通过模型图,我们可以看到,用户只要关注GIFComponent组件,和GIFComponent.ControllerOptions配置参数以及控制参数autoPlay和resetGif即可,非常简单!

1. 支持的功能列表如下

● 支持播放GIF图片。

● 支持控制GIF播放/暂停。

● 支持重置GIF播放动画。

● 支持调节GIF播放速率。

● 支持监听GIF所有帧显示完成后的回调。

● 支持设置显示大小。

● 支持7种不同的展示类型。

● 支持设置显示区域背景颜色。

2. 如何使用ohos-gif-drawable

首先需要使用npm下载ohos-gif-drawable三方库

npm install @ohos/ohos-gif-drawable --save

  

接下来我们需要配置一个worker给gifuct-js解码使用。

配置worker,在应用工程的entry/src/main/ets/pages目录下新建workers文件夹,并且创建文件 gifParseWorker.ts ,文件内容如下:

import arkWorker from '@ohos.worker';
import { handler } from '@ohos/ohos-gif-drawable/src/main/ets/components/gif/worker/GifWorker'
// handler封装了子线程逻辑,但worker目前只能在entry中进行创建arkWorker.parentPort.onmessage = handler;

  

然后在entry目录的build-profile.json5文件中,添加如下内容:

"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/pages/workers/gifParseWorker.ts"
]
}
},

  

到这里我们worker就配置好了。

下面就到了正式使用环节,我们只要在UI界面需要的地方写上自定义控件GIFComponent,然后传入GIFComponent.ControllerOptions,gifAutoPlay,gifReset这三个参数就能控制gif动画。

import { GIFComponent, ResourceLoader } from '@ohos/ohos-gif-drawable'
// gif绘制组件用户属性设置
@State model:GIFComponent.ControllerOptions = new GIFComponent.ControllerOptions();
// 是否自动播放
@State gifAutoPlay:boolean = true;
// 重置GIF播放,每次取反都能生效
@State gifReset:boolean = true;
// 在ARKUI的其他容器组件中添加该组件
GIFComponent({model:$model, autoPlay:$gifAutoPlay, resetGif:this.gifReset})

  

举个简单的例子说明一下

// 创建worker
let worker = new ArkWorker.Worker('entry/ets/pages/workers/gifParseWorker.ts', {type: 'classic',name: 'loadUrlByWorker'})
// 关闭动画
this.gifAutoPlay = false;
// 销毁上一次资源
this.model.destroy();
// 新创建一个modelx,用于配置用户参数
let modelx = new GIFComponent.ControllerOptions()
modelx
// 配置回调动画结束监听,和耗时监听
.setLoopFinish((loopTime) => {
this.gifLoopCount++;
this.loopHint = '当前gif循环了' + this.gifLoopCount + '次,耗时=' + loopTime + 'ms'
})
// 设置组件大小
.setSize({ width: this.compWidth, height: this.compHeight })
// 设置图像和组件的适配类型
.setScaleType(this.scaleType)
// 设置播放速率
.setSpeedFactor(this.speedFactor)
// 设置背景
.setBackgroundColor(Color.Grey)
// 加载网络图片,getContext(this)中的this指向page页面或者组件都可以ResourceLoader.downloadDataWithContext(getContext(this), { url: 'https://pic.ibaotu.com/gif/18/17/16/51u888piCtqj.gif!fwpaa70/fw/700' }, (sucBuffer) => {
// 网络资源sucBuffer返回后处理
modelx.loadBuffer(sucBuffer, () => { console.log('网络加载解析成功回调绘制!')
// 开启自动播放
this.gifAutoPlay = true;
// 给组件数据赋新的用户配置参数,达到后续gif动画效果
this.model = modelx; }, worker)}, (err) => {
// 用户根据返回的错误信息,进行业务处理(展示一张失败占位图、再次加载一次、加载其他图片等)
})

  

这里ResourceLoader内置了加载网络资源GIF,本地工程资源GIF和本地路径资源GIF文件数据的能力。

如果你已经有了GIF文件的arraybuffer数据,也可以直接调用modelx.loadBuffer(buffer: ArrayBuffer, readyRender: (err?) => void, worker: any)进行GIF播放。

甚至你已经生成了GIF解析数据,比如调用了2.2中的解码代码,那么你也可以直接调用modelx.setFrames(images?: GIFFrame[])来进行gif播放。

1.控制GIF的播放与暂停:

this.gifAutoPlay = true 开启动画
this.gifAutoPlay = false 暂停动画

  

组件内部会监听该参数的变化,用户只要改变值即可达到控制效果

2. 重置GIF的播放

this.gifReset = !this.gifReset 每次变化都会重置gif播放。

  

由于重置不需要状态管理,所以组件内监听到数据变化就会重置gif播放

3. 设置GIF动画播放速度


let modelx = new GIFComponent.ControllerOptions()
modelx.setSpeedFactor(2)// 将速率提升到2倍

  

调用setSpeedFactor(speed: number)即可调整播放速度speed 为对比原始速率的乘积因子,比如设置0.5即为原始速率的0.5倍,设置为2即为原始速率的2倍。

4. 监听GIF动画播放回调(比如第一次动画结束)和获取动画实际播放总时长

let modelx = new GIFComponent.ControllerOptions()
modelx.setLoopFinish((loopTime?) => {
// loopTime为GIF动画一周期耗时,回调时间为GIF动画一周期结束时间节点
})

  

调用setLoopFinish(fn: (loopTime?) => void)可以通过回调得到GIF动画运行一周期耗时和一周期结束时间节点。

5. 显示GIF任意一帧

let modelx = new GIFComponent.ControllerOptions()
modelx.setSeekTo(5) // 直接展示该gif第5帧图像

  

调用setSeekTo(gifPosition: number)可以直接展示该gif的某一帧图像。

到这里ohos-gif-drawable三方库的主要能力都介绍完了,是不是很简单呢!

6. 适配组件的大小

let modelx = new GIFComponent.ControllerOptions()

  

modelx.setScaleType(ScaleType.FIT_CENTER) // 将图像缩放适配组件大小调用setScaleType(scaletype: ScaleType)可以将图像和组件大小进行适配。

目前支持的类型如下图所示:

GIFComponent.ScaleType

为什么要配置worker

在具体实践过程中我们会发现,当我们按下解码按钮的时候,主界面会有一点卡顿的情况。特别是大的GIF文件进行解码的时候效果更明显。这是因为我们在主线程中进行了CPU的密集型计算,这是一个耗时且占用CPU的操作。主线程中是不能执行耗时操作的。但是JavaScript只有一个线程啊?那么解码这一块操作该如何处理会比较好呢?带着疑惑,我去查阅了资料发现JavaScript虽然属于单线程环境。但是通过引入Worker的能力,引入子线程worker,可以实现JavaScript的“多线程”技术。

OpenHarmony如何在子线程中处理耗时任务

为了争取良好的用户体验,我们需要将耗时操作封装至子线程中。

这里简单描述一下worker的能力:

能够让主页面运行的JavaScript线程中加载运行另外单独的一个或者多个JavaScript线程,但是它的多线程编程能力区别于传统意义上的多线程编程。主线程和Worker线程之间,不会共享任何作用域和资源,他们的通信方式是基于事件监听机制的 message。

接下来我们参考OpenHarmony文档下的worker能力

1. OpenHarmony环境下Worker的API接口列表

2. Worker的使用简单案例

经过了解之后,我们可以把解码的耗时封装到worker中处理,避免主线程耗时操作占用CPU导致卡顿问题。提升用户体验。

这也是使用ohos-gif-drawable三方库需要配置worker的原因。

扩展部分

GIF的滤镜效果

1. 灰白滤镜

//javascript
// 重点代码更改
let avg = (color[0] + color[1] + color[2]) / 3
patchData[pos] = avg;
patchData[pos + 1] = avg;
patchData[pos + 2] = avg;
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

  

2. 反转滤镜

//javascript
// 重点代码更改
patchData[pos] = 255 - color[0];
patchData[pos + 1] = 255 - color[1];
patchData[pos + 2] = 255 - color[2];
patchData[pos + 3] = colorIndex !== image.transparentIndex ? 255 : 0;

  

3. 高级滤镜效果

假设我们这边已经拿到了patch: Uint8ClampedArray像素数据,这里我需要先将其变换为一张PixelMap数据,参考GIFComponent中patch数据转换为PixelMap的代码。

//typescript
import image from "@ohos.multimedia.image"
let colorBuffer = patch.buffer
let pixelmap = await image.createPixelMap(colorBuffer, {
'size': {
'height': frame.dims.height as number,
'width': frame.dims.width as number
}
})

  

4. 高斯模糊

然后对PixelMap像素数据进行高斯模糊, 调用 `blur(pixelmap,10,true, (outPixelMap)=>{ // 模糊后的pixelmap数据})`在回调中获取模糊后的pixelmap。以下是模糊处理的算法:

export async function blur(bitmap: any, radius: number, canReuseInBitmap: boolean, func: AsyncTransform<PixelMap>) {
if (radius < 1) {
func("error,radius must be greater than 1 ", null);
return;
} let imageInfo = await bitmap.getImageInfo();
let size = {
width: imageInfo.size.width,
height: imageInfo.size.height
} if (!size) {
func(new Error("fastBlur The image size does not exist."), null)
return;
} let w = size.width;
let h = size.height;
var pixEntry: Array<PixelEntry> = new Array()
var pix: Array<number> = new Array() let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData); for (let index = 0; index < dataArray.length; index+=4) {
const r = dataArray[index];
const g = dataArray[index+1];
const b = dataArray[index+2];
const f = dataArray[index+3]; let entry = new PixelEntry();
entry.a = 0;
entry.b = b;
entry.g = g;
entry.r = r;
entry.f = f;
entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
pixEntry.push(entry);
pix.push(ColorUtils.rgb(entry.r, entry.g, entry.b));
} let wm = w - 1;
let hm = h - 1;
let wh = w * h;
let div = radius + radius + 1; let r = CalculatePixelUtils.createIntArray(wh);
let g = CalculatePixelUtils.createIntArray(wh);
let b = CalculatePixelUtils.createIntArray(wh); let rsum, gsum, bsum, x, y, i, p, yp, yi, yw: number;
let vmin = CalculatePixelUtils.createIntArray(Math.max(w, h)); let divsum = (div + 1) >> 1;
divsum *= divsum;
let dv = CalculatePixelUtils.createIntArray(256 * divsum);
for (i = 0; i < 256 * divsum; i++) {
dv[i]=(i / divsum);
} yw = yi =0;
let stack = CalculatePixelUtils.createInt2DArray(div,3);
let stackpointer, stackstart, rbs, routsum, goutsum, boutsum, rinsum, ginsum, binsum: number;
let sir: Array<number>;
let r1 = radius +1;
for(y =0; y < h; y++){
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
for(i =-radius; i <= radius; i++){
p = pix[yi + Math.min(wm, Math.max(i,0))];
sir = stack[i + radius];
sir[0]=(p &0xff0000)>>16;
sir[1]=(p &0x00ff00)>>8;
sir[2]=(p &0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0]* rbs;
gsum += sir[1]* rbs;
bsum += sir[2]* rbs;
if(i >0){
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
}else{
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius; for(x =0; x < w; x++){ r[yi]= dv[rsum];
g[yi]= dv[gsum];
b[yi]= dv[bsum]; rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum; stackstart = stackpointer - radius + div;
sir = stack[stackstart % div]; routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2]; if(y ==0){
vmin[x]= Math.min(x + radius +1, wm);
}
p = pix[yw + vmin[x]]; sir[0]=(p &0xff0000)>>16;
sir[1]=(p &0x00ff00)>>8;
sir[2]=(p &0x0000ff); rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2]; rsum += rinsum;
gsum += ginsum;
bsum += binsum; stackpointer =(stackpointer +1)% div;
sir = stack[(stackpointer)% div]; routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2]; rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2]; yi++;
}
yw += w;
}
for(x =0; x < w; x++){
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum =0;
yp =-radius * w;
for(i =-radius; i <= radius; i++){
yi = Math.max(0, yp)+ x; sir = stack[i + radius]; sir[0]= r[yi];
sir[1]= g[yi];
sir[2]= b[yi]; rbs = r1 - Math.abs(i); rsum += r[yi]* rbs;
gsum += g[yi]* rbs;
bsum += b[yi]* rbs; if(i >0){
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
}else{
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
} if(i < hm){
yp += w;
}
}
yi = x;
stackpointer = radius;
for(y =0; y < h; y++){
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi]=(0xff000000& pix[Math.round(yi)])|(dv[Math.round(rsum)]<<16)|(dv[
Math.round(gsum)]<<8)| dv[Math.round(bsum)]; rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum; stackstart = stackpointer - radius + div;
sir = stack[stackstart % div]; routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2]; if(x ==0){
vmin[y]= Math.min(y + r1, hm)* w;
}
p = x + vmin[y]; sir[0]= r[p];
sir[1]= g[p];
sir[2]= b[p]; rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2]; rsum += rinsum;
gsum += ginsum;
bsum += binsum; stackpointer =(stackpointer +1)% div;
sir = stack[stackpointer]; routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2]; rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2]; yi += w;
}
} let bufferNewData =newArrayBuffer(bitmap.getPixelBytesNumber());
let dataNewArray =newUint8Array(bufferNewData);
let index =0; for(let i =0; i < dataNewArray.length; i +=4){
dataNewArray[i]= ColorUtils.red(pix[index]);
dataNewArray[i+1]= ColorUtils.green(pix[index]);
dataNewArray[i+2]= ColorUtils.blue(pix[index]);
dataNewArray[i+3]= pixEntry[index].f;
index++;
}
await bitmap.writeBufferToPixels(bufferNewData);
if(func){
func("success", bitmap);
}
}

  

如果需要高级滤镜效果可以参考ImageKnife组件的transform部分,这里仅仅展示模糊效果。

由于滤镜效果目前ohos-gif-drawable三方库并没有开发接口提供出来,所以开发者可以根据实际需求重写自定义组件GIFComponent.,只需要在生成PixelMap的代码片段中加入滤镜代码,即可利用滤镜效果开发更多精彩的应用。

参考资料

1.《GIF文件格式解析》

https://segmentfault.com/a/1190000022866045

2.GIF解码库gifuct-js

https://github.com/matt-way/gifuct-js

3.GIF解码库底层逻辑jsBinarySchemaParser

https://github.com/matt-way/jsBinarySchemaParser

4.高级滤镜算法借鉴

https://gitee.com/openharmony-tpc/ImageKnife/tree/master/imageknife/src/main/ets/components/imageknife/transform

5.OpenHarmony环境下Worker的API接口列表

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis/js-apis-worker.md

6.Worker的使用简单案例

https://gitee.com/wang_zhaoyong/js_worker_module/wikis/Worker%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8

7.Web Worker API参考

https://developer.mozilla.org/zh-CN/docs/Web/API/Worker

8.OpenHarmony的Canvas文档

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-components-canvas-canvas.md

9.OpenHarmony的CanvasRenderingContext2D对象文档

https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-canvasrenderingcontext2d.md

OpenHarmony轻松玩转GIF数据渲染的更多相关文章

  1. 完整版的CAD技巧!3天轻松玩转CAD,零基础也能学会

    最近有很多小伙伴反应,CAD图纸学起来有点小困难,也许你还没能掌握技巧,CAD大神带你3天轻松玩转CAD,零基础也能快速学会. 一.看懂图纸是关键 CAD制图首先得让自己知道要绘制什么,如果心中对图纸 ...

  2. 2021 .NET Conf China 主题分享之-轻松玩转.NET大规模版本升级

    去年.NET Conf China 技术大会上,我给大家分享了主题<轻松玩转.NET大规模版本升级>,今天把具体分享的内容整理成一篇博客,供大家研究参考学习. 一.先说一下技术挑战和业务背 ...

  3. 【日常笔记】datatables表格数据渲染

    现在有很多表格渲染方式 这里只是记录怎么使用datatables渲染数据 使用datatables可以更方便的来渲染数据 [中文api]http://datatables.club/index.htm ...

  4. 基于AppCan MAS系统,如何轻松实现移动应用数据服务?

    完成一个移动应用开发,前端提供页面展示,当它要与一些业务系统进行交互,又该如何实现呢?2016AppCan移动开发者大会上,AppCan前端开发经理杨庆,分享了AppCan轻松实现移动应用数据服务的方 ...

  5. json数据渲染表单元素出现的问题

    解析页面表单元素 parseForm: function () { var data = {}; $(this).find('input').each(function () { switch ($( ...

  6. easyUI datagrid 多行多列数据渲染异常缓慢原因以及解决方法

    原因 最近,在优化之前公司帮联想(外包)做的一个老的后台管理系统,由于项目是基于easy UI框架,页面是后台用jsp实现的,再加上在公司推行前后端分离的实践,大部分项目都基于vue采用前后端分离去实 ...

  7. 解决vue数据渲染过程中的闪动问题

    关键代码 主要解决vue双大括号{{}}在数据渲染和加载过程中的闪动问题,而影响客服体验. html代码: <span class="tableTitle selftab" ...

  8. template.js 数据渲染引擎

    template.js 数据渲染引擎 template.js是一款JavaScript模板引擎,用来渲染页面的. 原理:提前将Html代码放进编写模板 <script id="tpl& ...

  9. wpgcms---单片页数据渲染

    单片页数据渲染,使用Twig的标签语法: <h1> {{ contentInfo.title }} </h1> {% autoescape false %} {{ conten ...

  10. vue2.* 目录结构分析 数据绑定 循环渲染数据 数据渲染02

    一.目录 结构分析 node_modules:项目依赖文件(也可以说是模块) src:开发时所用的资源 assets:静态资源文件 App.vue:根组件(最基础的公共页面) main.js:实例化v ...

随机推荐

  1. Qt+MPlayer音乐播放器开发笔记(二):交叉编译MPlayer以及部署到开发板播放演示

    前言   在ubuntu上arm交叉编译MPlayer播放器,并部署到开发板播放音乐.   Demo                Mplayer   MPlayer是一款开源多媒体播放器,以GNU通 ...

  2. pinject依赖注入模块

    pinject 是一个基于 Python 的轻量级依赖注入库,可以方便地实现依赖注入的功能. 下面我们将通过一个简单的示例来演示如何使用 pinject 实现依赖注入. 首先,我们需要安装 pinje ...

  3. SpringCloud使用Kafka消费者

    目录 POM文件配置 创建kafka配置 系统配置信息 启动入口 POM文件配置 <project xmlns="http://maven.apache.org/POM/4.0.0&q ...

  4. 大众点评cat报警源码

    类时序 时许说明 判断是否是报警机器. 1分钟启动一个线程根据设置的报警条件,时间段去查询CAT报告数据. 根据返回的报告数据,逐层解析TYPE,NAME,RANGE中的数据是否满足报警条件. 只有全 ...

  5. Arrays.asList的坑

    Arrays.asList 方法的坑 此方法接受可变个数的参数 构建一个ArrayList 可此ArrayList 非彼ArrayList ,他返回的是 Arrays 的一个内部类,实现了Abstra ...

  6. SpringCloud zookeeper和consul 的介绍和基本搭建

    1. Spring Cloud 集成 zookeeper Spring Cloud 集成了 zookeeper ,通过一些简单的注释,就可以快速注册到zookeeper 服务中 并且支持Spring ...

  7. Python回顾面向对象

    [一]面向过程开发和面向对象开发 [1]面向过程包括函数和面条 包括面条版本一条线从头穿到尾 学习函数后开始对程序进行分模块,分功能开发 学习模块化开发,我们就可以对我们的功能进行分类开发 建一个功能 ...

  8. KeyCastOW win10 屏幕显示按键 - 屏幕录像用

    KeyCastOW win10 屏幕显示按键 - 屏幕录像用 https://gitee.com/itas109/KeyCastOW/releases/v2.0.2.5-cn

  9. C++ 赋值运算符和拷贝构造函数

    拷贝构造函数 class Foo{ public: Foo(); Foo(const Foo&); //自己定义的拷贝构造函数 }; 如果不自己定义,编译器会自己合成一个默认拷贝构造函数: c ...

  10. 结构体、共用体与C++基础

    结构体.共用体与C++基础 1.结构体 结构体是C编程中一种用户自定义的数据类型,类似于Java的JavaBean //Student 相当于类名 //student和a 可以不定义,表示结构变量,也 ...