HarfBuzz 实战:五大核心API 实例详解【附iOS/Swift实战示例】

news/2025/11/1 21:19:46/文章来源:https://www.cnblogs.com/Con-Tch-LLYF/p/19183857

HarfBuzz 实战:五大核心API 实例详解【附iOS/Swift实战示例】

本文概述

本文是 HarfBuzz 系列的完结篇。

本文主要结合示例来讲解HarfBuzz中的核心API,不会面面俱到,只会介绍常用和重要的。

本文是HarfBuzz系列的第三篇,在阅读本文前,推荐先阅读以下两篇文章:

1)第一篇:HarfBuzz概览

2)第二篇:HarfBuzz核心概念

一、hb-blob

1)定义

blob 是一个抽象概念,是对一段二进制数据的封装,一般用来承载字体数据,在HarfBuzz中用 hb_blob_t 结构体表示。

2)hb_blob_create

hb_blob_t 的构造方法,签名如下:表示从一段二进制数据(u8序列)中创建

hb_blob_t *
hb_blob_create (const​ ​char​ *data,unsigned​ ​int​ length,hb_memory_mode_t​ mode,void​ *user_data,hb_destroy_func_t​ destroy);
  • data​:原始二进制数据,比如字体文件内容
  • length​:二进制长度
  • mode​:内存管理策略,即如何管理二进制数据,一般使用 HB_MEMORY_MODE_DUPLICATE 最安全,类型如下
模式 含义 优缺点
HB_MEMORY_MODE_DUPLICATE 复制模式,HarfBuzz会将传入的数据完整复制一份到私有内存 优点是不受传入的 data 生命周期影响缺点是多一次内存分配
HB_MEMORY_MODE_READONLY 只读模式,HarfBuzz会直接使用传入的数据,数据不会被修改 优点是无额外性能开销缺点是外部需要保证在 hb_blob_t 及其衍生的所有对象(如 hb_face_t)被销毁之前,始终保持有效且内容不变
HB_MEMORY_MODE_WRITABLE 可写模式,HarfBuzz会直接使用传入的指针,同时修改这块内存数据, 优点同READONLY缺点同READONLY,同时还可能修改数据
HB_MEMORY_MODE_READONLY_MAY_MAKE_WRITABLE 写时复制,HarfBuzz会直接使用传入的指针,在需要修改这块内存时才复制一份到私有内存 优点同READONLY缺点同READONLY,同时还可能修改数据
  • user_data​:可以通过 user_data 携带一些上下文
  • destroy​:blob释放时的回调

使用示例:

// 准备字体文件
let ctFont = UIFont.systemFont(ofSize: 18) as CTFont
let url = CTFontCopyAttribute(ctFont, kCTFontURLAttribute) as! URL
guard let fontData = try? Data(contentsOf: url) else {return
}
// 创建 HarfBuzz Blob 和 Face
// 'withUnsafeBytes' 确保指针在 'hb_blob_create' 调用期间是有效的。
// 'HB_MEMORY_MODE_DUPLICATE' 告诉 HarfBuzz 复制数据,这是在 Swift 中管理内存最安全的方式。
let blob = fontData.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) -> OpaquePointer? inlet charPtr = ptr.baseAddress?.assumingMemoryBound(to: CChar.self)return hb_blob_create(charPtr, UInt32(fontData.count), HB_MEMORY_MODE_DUPLICATE, nil, nil)
}

3)hb_blob_create_from_file

hb_blob_t 的构造方法,签名如下:表示从文件路径创建

hb_blob_t *
hb_blob_create_from_file (const char ​*file_name);
  • file_name​:文件绝对路径,注意非文件名

使用示例:

let ctFont = UIFont.systemFont(ofSize: 18) as CTFont
let url = CTFontCopyAttribute(ctFont, kCTFontURLAttribute) as! URL
let blob = url.path.withCString { ptr inhb_blob_create_from_file(ptr)
}

查看 hb_blob_create_from_file 函数实现,会通过 mmap 的方式来映射字体文件,可以共享系统的字体内存缓存,相比自己读取二进制数据来创建blob来说,这种方式会少一次IO,且内存占用也可能更小(复用系统内存缓存)。

二、hb-face

1)定义

face 表示一个单独的字体,它会解析blob中的二进制字体数据,通过face可以访问字体中的各种table,如GSUB、GPOS、cmap表等,在HarfBuzz中用 hb_face_t 结构体表示。

2)hb_face_create

hb_face_t的构造方法,签名如下:表示从一段字体二进制数据中构造face

hb_face_t *
hb_face_create (hb_blob_t​ ​*blob,unsigned int index);
  • blob:字体数据
  • index:有的字体文件是一个字体集合(ttc),index表示使用第几个字体数据来创建face;对于单字体文件(ttf)来说,index传0即可

关于字体更多知识可以参考:一文读懂字体文件

3)hb_face_reference

hb_face_t的引用计数 +1

hb_face_t *
hb_face_reference (hb_face_t​ ​*face);

3)hb_face_destroy

hb_face_t的引用计数 -1,注意不是直接销毁对象,在HarfBuzz中,所有对象类型都提供了特定的生命周期管理API(create、reference、destroy),对象采用引用计数方式管理生命周期,当引用计数为0时才会释放内存

void
hb_face_destroy (hb_face_t​ ​*face);

在实际使用时,需要注意调用顺序,需要保证所有从face创建出的对象销毁之后,再调用hb_face_destroy。

4)hb_face_get_upem

获取字体的upem。

unsigned int
hb_face_get_upem (const ​hb_face_t​ ​*face);

upem 即 unitsPerEm,在字体文件中一般存储在 head 表中,字体的upem通常很大(一般是1000或2048),其单位并不是像素值,而是 em unit,<unitsPerEm value="2048"/> 表示 2048 units = 1 em = 设计的字高,比如当字体在屏幕上以 16px 渲染时,1 em = 16px,其他数值可按比例换算。

5)hb_face_reference_table

从字体中获取原始的table数据,这个函数返回的是table数据的引用,而不是拷贝,所以这个函数几乎没有性能开销;如果对应 tag 的table不存在,会返回一个空的blob,可以通过 hb_blob_get_length 来检查获取是否成功。

hb_blob_t *
hb_face_reference_table (const ​hb_face_t​ ​*face,hb_tag_t​ tag);

使用示例:

// 构造tag,这里是获取head表
let headTag = "head".withCString { ptr inhb_tag_from_string(ptr, -1)
}
let headBlob = hb_face_reference_table(face, headTag);
// 检查是否成功
if (hb_blob_get_length(headBlob) > 0) {// 获取原始数据指针并解析var length: UInt32 = 0let ptr = hb_blob_get_data(headBlob, &length);// ... 在这里执行自定义解析 ...
}
// 必须销毁返回的 blob!
hb_blob_destroy(headBlob);

6)hb_face_collect_unicodes

获取字体文件支持的所有Unicode,这个函数会遍历cmap表,收集cmap中定义的所有code point。

void
hb_face_collect_unicodes (hb_face_t​ ​*face,hb_set_t​ ​*out);

可以用收集好的结果来判断字体文件是否支持某个字符,这在做字体回退时非常有用。

使用示例:

let set = hb_set_create()
hb_face_collect_unicodes(face, set)
var cp: UInt32 = 0
while hb_set_next(set, &cp) == 1 {print("code point: ", cp)
}
hb_set_destroy(set)

三、hb-font

1)定义

font 表示字体实例,可以在face的基础上,设置字号、缩放等feature来创建一个font,在HarfBuzz中用 hb_font_t 结构体表示。

2)hb_font_create & hb_font_reference & hb_font_destroy

hb_font_t 的创建、引用、销毁函数,整体同face对象一样,采用引用计数的方式管理生命周期。

3)hb_font_get_glyph_advance_for_direction

获取一个字形在指定方向上的默认前进量(advance)

void
hb_font_get_glyph_advance_for_direction(hb_font_t​ *font,hb_codepoint_t​ glyph,hb_direction_t​ direction,hb_position_t​ *x,hb_position_t​ *y);
  • font:指定字体
  • glyph:目标字形
  • direction:指定方向,HB_DIRECTION_LTR/HB_DIRECTION_LTR/HB_DIRECTION_TTB/HB_DIRECTION_BTT
  • x:返回值,advance.x
  • y:返回值,advance.y

这个函数会从 hmtx(横向)或vmtx(纵向)表中读取advance。

一般情况下,我们不需要直接使用这个函数,这个函数是直接查表返回静态的默认前进量,但实际塑形时,一般还涉及kerning等调整,所以一般常用hb_shape()的返回值,hb_shape()返回的是包含字形上下文调整(如kerning)等的结果。

使用示例:

let glyph_A: hb_codepoint_t = 65
var x_adv: hb_position_t = 0
var y_adv: hb_position_t = 0
// 1. 获取 'A' 在水平方向上的前进位移
hb_font_get_glyph_advance_for_direction(font,glyph_A,HB_DIRECTION_LTR, // 水平方向&x_adv,&y_adv)

4)hb_font_set_ptem & hb_font_get_ptem

设置和获取字体大小(point size),ptem 即 points per Em,也就是 iOS 中的 point size

void
hb_font_set_ptem (hb_font_t​ ​*font,float ptem);

这个函数是 hb_font_set_scale() 简易封装,在HarfBuzz内部,字体大小不是用 points 来存储的,而是用一个称为 scale 的 26.6 的整数格式来存储的。

使用示例:

// 设置字体大小为 18 pt
hb_font_set_ptem(myFont, 18.0f);// 等价于
// 手动计算 scale
int32_t scale = (int32_t)(18.0f * 64); // scale = 1152
// 手动设置 scale
hb_font_set_scale(myFont, scale, scale);

Q:什么是 26.6 整数格式?

"26.6" 格式是一种定点数(Fixed-Point Number)表示法,用于将浮点数转换成整数存储和运算;在 HarfBuzz 中,这个格式用于 hb_position_t 类型(int32_t),用来表示所有的坐标和度量值(如字形位置、前进量等)。

26.6 表示将一个 32 位整数划分为:高26位用于存储整数部分(一个有符号的 25 位整数 + 1 个符号位)+ 低6位用于存储小数部分。

换算规则:2^6 = 64

  • 从「浮点数」转为「26.6 格式」:hb_position_t = (float_value * 64)
  • 从「26.6 格式」转回「浮点数」:float_value = hb_position_t / 64.0

那为什么不直接用整数呢,因为文本布局需要极高的精度,如果只用整数,那任何小于1的误差都会被忽略,在一行文本中累计下来,误差就很大了。

那为什么不直接用浮点数呢,因为整数比浮点数的运算快,且浮点数在不同平台上存储和计算产生的误差还确定。

因此为了兼顾性能和精确,将浮点数「放大」成整数参与计算。

5)hb_font_get_glyph

用于查询指定 unicode 在字体中的有效字形(glyph),这在做字体回退时非常有用。

hb_bool_t
hb_font_get_glyph (hb_font_t​ ​*font,hb_codepoint_t​ unicode,hb_codepoint_t​ variation_selector,hb_codepoint_t​ ​*glyph);
  • 返回值 hb_bool_t:true 表示成功,glyph 被设置有效字形,false 表示失败,即字体不支持该 unicode
  • font:字体
  • unicode:待查询 unicode
  • variation_selector:变体选择符的code point,比如在 CJK 中日韩表意文字中,一个汉字可能有不同的字形(如下图),一个字体可能包含这些所有的变体,那我们可以通过 variation_selector 指定要查询哪个变体;如果只想获取默认字形,那该参数可传 0

在这里插入图片描述

  • glyph:返回值,用于存储 unicode 对应字形

当然,还有与之对应的批量查询的函数:hb_font_get_nominal_glyphs

四、hb-buffer

1)定义

buffer 在HarfBuzz中表示输入输出的缓冲区,用 hb_buffer_t 结构体表示,一般用于存储塑形函数的输入和塑形结束的输出。

2)hb_buffer_create & hb_buffer_reference & hb_buffer_destroy

hb_buffer_t 的创建、引用、销毁函数,整体同face对象一样,采用引用计数的方式管理生命周期。

3)hb_buffer_add_utf8 & hb_buffer_add_utf16 & hb_buffer_add_utf32

将字符串添加到buffer,使用哪个函数取决于字符串编码方式。

void
hb_buffer_add_utf8 (hb_buffer_t​ *buffer,const​ ​char​ *text,int​ text_length,unsigned​ ​int​ item_offset,int​ item_length);
  • buffer:目标buffer
  • text:文本
  • text_length:文本长度,传 -1 会自动查找到字符串末尾的 \0
  • item_offset:偏移量,0 表示从头开始
  • item_length:添加长度,-1 表示全部长度

使用示例:

let buffer = hb_buffer_create()
let text = "Hello World!"
let cText = text.cString(using: .utf8)!
hb_buffer_add_utf8(buffer, cText, -1, 0, -1)

4)hb_buffer_guess_segment_properties

猜测并设置buffer的塑形属性(script、language、direction等)。

void
hb_buffer_guess_segment_properties (hb_buffer_t​ ​*buffer);

这个函数一般取第一个字符的属性作为整体buffer的属性,所以如果要使用这个函数来猜测属性的话,需要保证字符串已经被提前分段。

当然也可以手动调用hb_buffer_set_script、hb_buffer_set_language 等来手动设置。

五、hb-shape

hb_shape是HarfBuzz的核心塑形函数,签名如下:

void
hb_shape (hb_font_t​ *font,hb_buffer_t​ *buffer,const​ ​hb_feature_t​ *features,unsigned​ ​int​ num_features);
  • font:用于塑形的字体实例,需要提前设置好字体大小等属性
  • buffer:既是输入,待塑形的字符串会通过buffer传入;也是输出,塑形完成后,塑形结果会通过buffer返回
  • features:feature数组,用于启用或禁用字体中的某些特性,不需要的话可以传nil
  • num_features:上一参数features数组的数量

hb_shape 会执行一系列复杂操作,比如:

  • 字符到字形映射:查询cmap表,将字符转换为字形
  • 字形替换:查询 GSUB 表,进行连字替换、上下文替换等
  • 字形定位:查询 GPOS 表,微调每个字形的位置,比如kerning,标记定位,草书连接等

详细的塑形操作可以参考HarfBuzz核心概念。

下面重点介绍塑形结果,可以通过 hb_buffer_get_glyph_infos 和 hb_buffer_get_glyph_positions 从buffer中获取塑形结果。

hb_buffer_get_glyph_infos 签名如下:

// hb_buffer_get_glyph_infos
hb_glyph_info_t *
hb_buffer_get_glyph_infos (hb_buffer_t​ ​*buffer,unsigned int ​*length);typedef struct {hb_codepoint_t codepoint;uint32_t       cluster;
} hb_glyph_info_t;

hb_buffer_get_glyph_infos 返回一个 hb_glyph_info_t 数组,用于获取字形信息,hb_glyph_info_t 中有两个重要参数:

  • codepoint:glyphID,注意这里不是 unicode 码点
  • cluster:映射回原始字符串的字节索引

这里需要展开介绍下cluster:

  • 在连字 (多对一)情况下:比如 "f" 和 "i" (假设在索引 0 和 1) 被塑形为一个 "fi" 字形。这个 "fi" 字形的 cluster 值会是 0(即它所代表的第一个字符的索引)
  • 拆分 (一对多)情况下:在某些语言中,一个字符可能被拆分为两个字形,这两个字形都会有相同的 cluster 值,都指向那个原始字符
  • 高亮与光标:当我们需要高亮显示原始文本的第 3 到第 5 个字符时,就是通过 cluster 值来查找所有 cluster 在 3 和 5 之间的字形,然后绘制它们的选区

hb_buffer_get_glyph_positions 的签名如下:

hb_glyph_position_t *
hb_buffer_get_glyph_positions (hb_buffer_t​ ​*buffer,unsigned int ​*length);typedef struct {hb_position_t  x_advance;hb_position_t  y_advance;hb_position_t  x_offset;hb_position_t  y_offset;
} hb_glyph_position_t;

hb_buffer_get_glyph_positions 返回一个 hb_glyph_position_t 的数组,用于获取字形的位置信息,hb_glyph_position_t 参数有:

  • x_advance / y_advance:x / y 方向的前进量;前进量指的是绘制完一个字形后,光标应该移动多远继续绘制下一个字形;对于横向排版而言,y_advance 一般是0;需要注意的是 advance 值中已经包含了 kernig 的计算结果
  • x_offset / y_offset:x / y 方向的绘制偏移,对于带重音符的字符如 é 来说,塑形时可能拆分成 e + ´,重音符 ´ 塑形结果往往会带 offset,以保证绘制在 e 的上方

position主要在排版/绘制时使用,以绘制为例,通常用法如下:

// (x, y) 是“笔尖”或“光标”位置
var current_x: Double = 0.0 
var current_y: Double = 0.0// 获取塑形结果
var glyphCount: UInt32 = 0
let infos = hb_buffer_get_glyph_infos(buffer, &glyphCount)
let positions = hb_buffer_get_glyph_positions(buffer, &glyphCount)// 遍历所有输出的字形
for i in 0..<Int(glyphCount) {let info = infos[i]let pos = positions[i]// 1. 计算这个字形的绘制位置 (Draw Position)//    = 当前光标位置 + 本字形的偏移let draw_x = current_x + (Double(pos.x_offset) / 64.0)let draw_y = current_y + (Double(pos.y_offset) / 64.0)// 2. 在该位置绘制字形//    (info.codepoint 就是字形 ID)drawGlyph(glyphID: info.codepoint, x: draw_x, y: draw_y)// 3. 将光标移动到下一个字形的起点//    = 当前光标位置 + 本字形的前进位移current_x += (Double(pos.x_advance) / 64.0)current_y += (Double(pos.y_advance) / 64.0)
}

六、完整示例

下面我们以 Swift 中调用 HarfBuzz 塑形一段文本为例:

func shapeTextExample() {// 1. 准备字体let ctFont = UIFont.systemFont(ofSize: 18) as CTFontlet url = CTFontCopyAttribute(ctFont, kCTFontURLAttribute) as! URL// 2. 从字体文件路径创建bloblet blob = url.path.withCString { ptr inhb_blob_create_from_file(ptr)}guard let face = hb_face_create(blob, 0) else { // 0 是字体索引 (TTC/OTF collections)print("无法创建 HarfBuzz face。")hb_blob_destroy(blob) // 即使失败也要清理return}// Blob 已经被 face 引用,现在可以安全销毁hb_blob_destroy(blob)// --- 3. 创建 HarfBuzz 字体对象 ---guard let font = hb_font_create(face) else {print("无法创建 HarfBuzz font。")hb_face_destroy(face)return}// 告诉 HarfBuzz 使用其内置的 OpenType 函数来获取字形等信息// 这对于 OpenType 字体(.otf, .ttf)是必需的hb_ot_font_set_funcs(font)hb_font_set_synthetic_slant(font, 1.0)// 设置字体大小 (例如 100pt)。// HarfBuzz 内部使用 26.6 整数坐标系,即 1 单位 = 1/64 像素。let points: Int32 = 100let scale = points * 64hb_font_set_scale(font, scale, scale)// --- 4. 创建 HarfBuzz 缓冲区 ---guard let buffer = hb_buffer_create() else {print("无法创建 HarfBuzz buffer。")hb_font_destroy(font)hb_face_destroy(face)return}// --- 5. 添加文本到缓冲区 ---let text = "Hello World!"let cText = text.cString(using: .utf8)!// hb_buffer_add_utf8:// - buffer: 缓冲区// - cText: UTF-8 字符串指针// - -1: 字符串长度 (传 -1 表示自动计算直到 null 终止符)// - 0: item_offset (从字符串开头)// - -1: item_length (处理整个字符串)hb_buffer_add_utf8(buffer, cText, -1, 0, -1)// 猜测文本属性 (语言、文字方向、脚本)// 这对于阿拉伯语 (RTL - 从右到左) 至关重要!hb_buffer_guess_segment_properties(buffer)// --- 6. 执行塑形 (Shape!) ---// 使用 nil 特征 (features),表示使用字体的默认 OpenType 特征hb_shape(font, buffer, nil, 0)// --- 7. 获取塑形结果 ---var glyphCount: UInt32 = 0// 获取字形信息 (glyph_info)let glyphInfoPtr = hb_buffer_get_glyph_infos(buffer, &glyphCount)// 获取字形位置 (glyph_position)let glyphPosPtr = hb_buffer_get_glyph_positions(buffer, &glyphCount)guard glyphCount > 0, let glyphInfo = glyphInfoPtr, let glyphPos = glyphPosPtr else {print("塑形失败或没有返回字形。")// 清理hb_buffer_destroy(buffer)hb_font_destroy(font)hb_face_destroy(face)return}print("\n--- 塑形结果 for '\(text)' (\(glyphCount) glyphs) ---")// --- 8. 遍历并打印结果 ---// 'cluster' 字段将字形映射回原始 UTF-8 字符串中的字节索引。// 这对于高亮显示、光标定位等非常重要。var currentX: Int32 = 0var currentY: Int32 = 0// 注意:阿拉伯语是从右到左 (RTL) 的。// hb_buffer_get_direction(buffer) 会返回 HB_DIRECTION_RTL。// HarfBuzz 会自动处理布局,所以我们只需按顺序迭代字形。for i in 0..<Int(glyphCount) {let info = glyphInfo[i]let pos = glyphPos[i]let glyphID = info.codepoint // 这是字形 ID (不是 Unicode 码点!)let cluster = info.cluster  // 映射回原始字符串的字节索引let x_adv = pos.x_advance   // X 轴前进let y_adv = pos.y_advance   // Y 轴前进let x_off = pos.x_offset    // X 轴偏移 (绘制位置)let y_off = pos.y_offset    // Y 轴偏移 (绘制位置)print("Glyph[\(i)]: ID=\(glyphID)")print("  Cluster (string index): \(cluster)")print("  Advance: (x=\(Double(x_adv) / 64.0), y=\(Double(y_adv) / 64.0)) pt") // 除以 64 转回 ptprint("  Offset:  (x=\(Double(x_off) / 64.0), y=\(Double(y_off) / 64.0)) pt")print("  Cursor pos before draw: (x=\(Double((currentX + x_off)) / 64.0), y=\(Double((currentY + y_off)) / 64.0)) pt")// 累加光标位置currentX += x_advcurrentY += y_adv}print("------------------------------------------")print("Total Advance: (x=\(currentX / 64), y=\(currentY / 64)) pt")// --- 9. 清理所有 HarfBuzz 对象 ---// 按照创建的相反顺序销毁hb_buffer_destroy(buffer)hb_font_destroy(font)hb_face_destroy(face)print("✅ 塑形和清理完成。")
}

输出结果如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/953016.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java 获取 MultipartFile

项目中有时候会需要调用别人的接口上传一个文件,别人的接口参数为 MultipartFile 类型,需要对 File 文件进行一个转换再进行上传。 这里整理了一个较为实用的方法: public static MultipartFile ofMultipartFile(Fi…

革命性的智能文档处理与问答引擎

革命性的智能文档处理与问答引擎引言:为什么需要更好的RAG解决方案? 在人工智能飞速发展的今天,大型语言模型(LLMs)在各类任务中展现出惊人能力,但“幻觉”问题、知识更新滞后以及专业领域理解不足等局限性也逐渐…

20251101

还是js有趣,比javaweb好玩多了,今天稍微试着用canvas写了写方块的移动逻辑,包括左右移动,左右加速度,跳跃,重力加速度,着陆检测等。虽然有很多bug,还是比较有收获的。 ``

第12天(中等题 越长越合法滑动窗口)

打卡第十二天 2道中等题不定长滑动窗口: 越长越合法 一般要写 ans += left。 内层循环结束后,[left,right] 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,[left−1,right] 是满足题目要求的。由于…

正式发布!2025年11月广州心理咨询机构哪家专业?

正式发布!2025年11月广州心理咨询机构哪家专业?在广州,随着社会节奏的加快和人们生活压力的增大,心理咨询的需求日益增长。对于那些在家庭、情感、亲子等方面遇到困扰的人们来说,寻找一家专业的心理咨询机构至关重…

大模型开发 - 02 Spring AI Concepts - 详解

大模型开发 - 02 Spring AI Concepts - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", &quo…

2025 年 11 月降膜蒸发器,结晶蒸发器,真空浓缩器厂家最新推荐,聚焦资质、案例、售后的五家机构深度解读

为帮助企业精准挑选降膜蒸发器、结晶蒸发器、真空浓缩器,本次推荐由十家行业专业机构联合开展测评,聚焦厂家资质、实际案例与售后服务三大核心维度。测评中,机构对各厂家的生产资质、技术认证等进行严格核查,确保符…

2025 年 11 月废水蒸发器,多效蒸发器,低温蒸发器厂家最新推荐,产能、专利、环保三维数据透视

为助力企业精准选择废水蒸发器、多效蒸发器、低温蒸发器产品,本次测评联合行业权威协会开展,采用 “产能 - 专利 - 环保” 三维数据评估体系。测评过程中,对各厂家近三年产能规模、设备交付效率进行量化分析,核查专…

Java方法——可变参数

Java方法——可变参数可变参数 可变参数也称不定项参数 在方法声明中,在指定参数类型后加一个省略号... 一个方法只能指定一个可变参数,它必须是方法的最后一个参数,任何普通参数都需要在其之前声明 public class d…

2025 年 11 月曝气器厂家最新推荐,专业制造与品牌保障口碑之选

在环保水处理行业,曝气器的专业制造水平决定产品性能底线,品牌口碑则反映市场长期认可程度。为帮助采购方精准锁定兼具专业实力与良好口碑的厂家,行业权威协会于 2025 年 11 月开展曝气器厂家专项测评。本次测评以 …

sigmoid函数求导

sigmoid函数求导\[f(x)=\frac{1}{1+e^{-x}}=\frac{e^x}{e^x+1}=1-(e^x+1)^{-1}\\f(x)=\frac{e^x}{(e^x+1)^2}=\frac{e^xe^{-2x}}{(1+e^{-x})^2}=\frac{1}{1+e^{-x}}\frac{e^{-x}}{1+e^{-x}}=f(x)(1-f(x)) \]

[20251028]SQLPlus的行编辑器.txt

[20251028]SQLPlus的行编辑器.txt--//学习SQLPlus的行编辑器.看了一个网上的教学视频,对方使用SQLPlus的行编辑器,估计许多人基本不会,感觉有时候利用它效率还--//是很高的,查一些资料做一个记录。--//基本就5个命…

【深基7.例4】歌唱比赛

【深基7.例4】歌唱比赛P5738 【深基7.例4】歌唱比赛 题目描述 n(n≤100)名同学参加歌唱比赛,并接受 m(m≤20)名评委的评分,评分范围是0到10分。这名同学的得分就是这些评委给分中去掉一个最高分,去掉一个最低分,剩…

2025 年 11 月曝气器厂家最新推荐,聚焦资质、案例、售后的优质品牌深度解读

在环保水处理设备采购中,曝气器厂家的资质合规性、项目案例丰富度及售后服务完善性,是保障设备长期稳定运行的关键。为帮助采购方避开选择误区,行业权威协会于 2025 年 11 月启动曝气器厂家专项测评,本次测评创新性…

美团多智能体WOWService智能系统概要

美团多智能体WOWService智能系统概要我们都曾被“人工智障”客服惹恼过 相信很多人都有过这样的经历:满怀希望地向在线客服求助,结果却遇到一个只会重复“您的问题我无法理解”或提供牛头不对马嘴答案的聊天机器…

Python 潮流周刊#125:个人 AI 笔记本工具

本周刊由 Python猫 出品,精心筛选国内外的 400+ 信息源,为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景:帮助所有读者精进 Python 技术,并增长职业和副业的收入。 温馨提…

Java方法——方法的重载、命令行传参

Java方法——方法的重载、命令行传参方法的重载 定义:重载就是在一个类中,有相同的函数名称,但形参不同 方法的重载规则:方法名称需要相同; 参数列表必须不同(个数不同,类型不同,参数排列顺序不同) 方法的返回…

2025 年 11 月石灰料仓厂家最新推荐,聚焦资质、案例、售后的五家机构深度解读

随着环保行业对药剂存储设备要求的不断提升,石灰料仓作为污水及污泥处理环节的核心设备,其质量与服务水平备受关注。为帮助行业用户精准筛选优质厂家,本次推荐基于专业环保设备协会最新测评数据,从厂家资质认证、实…