跳到主要内容

数组与字符串操作

数组与字符串是 PLC 编程中最常用的数据类型。本章详细说明 SCL 中数组声明、初始化、访问、切片操作,以及字符串的声明、处理函数与格式化转换,并给出常见陷阱与工业现场最佳实践。


1. ARRAY 声明

1.1 一维数组

VAR
// 声明显式上下界
a1 : ARRAY[0..9] OF INT; // 10 个元素, 索引 0 ~ 9
a2 : ARRAY[1..10] OF INT; // 10 个元素, 索引 1 ~ 10
a3 : ARRAY[-5..5] OF REAL; // 11 个元素, 索引 -5 ~ 5

// 常用模式: 从 0 或 1 开始
dataBuf : ARRAY[0..255] OF BYTE; // 通信缓冲区
axisPos : ARRAY[1..8] OF LREAL; // 8 轴位置数组
END_VAR

上下界规则:

  • 下界和上界都是 DINT 范围的整数常量(或常量表达式),不能是变量。
  • 下界可以小于、等于或大于 0;负数索引在需要查表偏移时很实用。
  • 元素个数 = 上界 - 下界 + 1

1.2 二维数组

VAR
// 二维数组: 行优先 (ROW-MAJOR)
mat : ARRAY[1..3, 1..4] OF INT; // 3 行 × 4 列 = 12 个元素

// 矩阵应用
matrix3x3 : ARRAY[1..3, 1..3] OF LREAL; // 3×3 变换矩阵
ioTbl : ARRAY[1..16, 1..8] OF BOOL; // 16 模块 × 8 通道
END_VAR

1.3 三维数组

VAR
cube : ARRAY[1..5, 1..6, 1..7] OF INT; // 5×6×7 = 210 个元素
END_VAR

1.4 常见陷阱

陷阱说明
边界写错ARRAY[0..10] 是 11 个元素,不是 10 个
负索引溢出下界为负数时,索引运算要小心符号错误
数组大小超出内存大型 ARRAY[*]ARRAY[0..10000] 可能超出任务可用内存,需检查剩余内存

2. 数组初始化

2.1 字面量初始化

VAR
// 一维初始化
a1 : ARRAY[0..4] OF INT := [10, 20, 30, 40, 50];

// 二维初始化 (行优先)
a2 : ARRAY[1..2, 1..3] OF INT := [
[1, 2, 3],
[4, 5, 6]
];

// 三维初始化
a3 : ARRAY[1..2, 1..2, 1..2] OF INT := [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];
END_VAR

2.2 重复语法

VAR
// 10 个元素全部初始化为 0
zeros : ARRAY[0..9] OF INT := [10(0)];

// 混合: 前 3 个 100, 后 7 个 0
mixed : ARRAY[0..9] OF INT := [3(100), 7(0)];

// 二维重复: 全部 0
mat : ARRAY[1..3, 1..4] OF INT := [12(0)];
END_VAR

重复语法 [N(value)] 展开为 N 个连续的 value,在需要大数组初始化为统一值时非常高效。

2.3 常见陷阱

陷阱说明
元素个数不匹配初始值列表长度必须等于数组元素个数,否则编译错误
重复语法嵌套二维数组不能写 [3([4(0)])],必须展开或 [12(0)]
初始化覆盖声明时初始化与运行时赋值会覆盖,注意时序

3. 数组索引

3.1 索引方式

PROGRAM Main
VAR
arr : ARRAY[0..9] OF INT := [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
val : INT;
i : INT;
END_VAR

// 直接索引
val := arr[0]; // 10
val := arr[9]; // 100

// 变量索引
i := 5;
val := arr[i]; // 60

// 表达式索引
val := arr[i - 1]; // 50
END_PROGRAM

3.2 零基 vs 一基

特性零基 [0..N-1]一基 [1..N]
习惯嵌入式/C 背景工程师传统 PLC 工程师
循环FOR i := 0 TO 9FOR i := 1 TO 10
偏移量索引 = 偏移索引 = 偏移 + 1
建议数据缓冲区、通信协议轴号、模块号、通道号

DarraRT 建议: 统一团队风格。数据缓冲区用零基,物理设备编号用一基。不要在同一个项目中混用两种风格。

3.3 边界检查

// 安全写法: 索引前检查边界
IF idx >= LOWER_BOUND(arr, 1) AND idx <= UPPER_BOUND(arr, 1) THEN
val := arr[idx];
ELSE
// 记录错误日志, 不访问
LogError('数组索引越界', idx);
END_IF

运行时边界检查:

  • 调试模式下默认启用,越界触发运行时异常。
  • 发布模式下可以关闭以提升性能,但建议始终保留(工业现场数据越界可能导致不可预测的后果)。
  • 使用 LOWER_BOUND / UPPER_BOUND 函数获取数组边界,不要硬编码。

4. 多维数组

4.1 行优先顺序

SCL 多维数组采用 行优先 (Row-Major) 存储,即最右侧下标变化最快。

VAR
mat : ARRAY[1..3, 1..4] OF INT;
row, col : INT;
idx : INT;
END_VAR

// 行优先遍历: 外循环行, 内循环列
FOR row := 1 TO 3 DO
FOR col := 1 TO 4 DO
mat[row, col] := (row - 1) * 4 + (col - 1);
END_FOR;
END_FOR;

// 等价于线性访问:
// mat[1,1]=0, mat[1,2]=1, ..., mat[3,4]=11
END_PROGRAM

4.2 嵌套循环遍历

VAR
cube : ARRAY[1..5, 1..6, 1..7] OF REAL;
x, y, z : INT;
sum : REAL;
END_VAR

sum := 0.0;
FOR x := 1 TO 5 DO
FOR y := 1 TO 6 DO
FOR z := 1 TO 7 DO
sum := sum + cube[x, y, z];
END_FOR;
END_FOR;
END_FOR;
END_PROGRAM

4.3 常见陷阱

陷阱说明
索引顺序写反mat[row, col]mat[col, row] 语义完全不同
大维数组性能三维以上数组嵌套循环性能开销大,考虑用一维线性化替代
内存布局依赖不要依赖具体的存储布局做指针运算(SCL 不保证)

5. 数组切片 (DarraRT 扩展)

DarraRT SCL 支持数组切片语法,用于快速取子数组。

VAR
src : ARRAY[0..9] OF INT := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
dst : ARRAY[0..4] OF INT;
sub : ARRAY[0..2] OF INT;
END_VAR

// 取连续片段: src[2..4] => [2, 3, 4]
dst[0..2] := src[2..4];

// 切片赋值
dst[0..4] := src[5..9]; // [5, 6, 7, 8, 9]

// 步长切片 (DarraRT 扩展)
// sub := src[0..8 STEP 2]; // [0, 2, 4, 6, 8] (如支持)
END_PROGRAM

约束:

  • 切片两端必须为常量或变量,且切片长度必须与目标长度匹配。
  • 切片不能跨任务或跨 FB 实例传递(仅限局部使用)。
  • 步长切片为 DarraRT 扩展,跨平台移植时需注意兼容性。

6. 数组整体赋值

VAR
a : ARRAY[0..4] OF INT := [1, 2, 3, 4, 5];
b : ARRAY[0..4] OF INT;
c : ARRAY[0..9] OF INT;
END_VAR

// 整数组拷贝: 类型和大小必须完全一致
b := a; // OK: b = [1, 2, 3, 4, 5]

// 以下会编译错误: 大小不匹配
// c := a; // ERROR: 大小不同

// 逐元素拷贝 (大小不同时的替代方案)
FOR i := 0 TO 4 DO
c[i] := a[i];
END_FOR;
END_PROGRAM

规则:

  • 整数组赋值要求左右两侧的类型和维数大小完全一致
  • 结构体数组同样支持整体赋值(要求结构体类型一致)。
  • 如果数组大小不同,必须用FOR循环逐元素拷贝。

7. ARRAY[*] 变长数组

FUNCTION SumArray : REAL
VAR_INPUT
arr : ARRAY[*] OF REAL; // 变长数组参数
END_VAR
VAR
i : DINT;
s : REAL;
END_VAR

s := 0.0;
FOR i := LOWER_BOUND(arr, 1) TO UPPER_BOUND(arr, 1) DO
s := s + arr[i];
END_FOR;
SumArray := s;
END_FUNCTION

约束:

  • ARRAY[*] 只能用于 函数/功能块的形式参数,不能用于全局变量或局部变量。
  • 在函数体内必须通过 LOWER_BOUND / UPPER_BOUND 获取实际边界,不能硬编码。
  • 调用时传入不同大小的数组不出错,函数自动适配。
  • 不支持 ARRAY[*, *] 二维变长(仅支持一维)。

8. STRING 声明

8.1 基本声明

VAR
// 默认长度: 80 字符
s1 : STRING; // 等效 STRING[80]

// 指定最大长度
s2 : STRING[20]; // 最大 20 字符
s3 : STRING[255]; // 最大 255 字符

// 声明时初始化
s4 : STRING[20] := 'Hello PLC';
s5 : STRING[100] := '工业自动化控制';

// WSTRING (Unicode)
ws1 : WSTRING; // 默认 80 个宽字符
ws2 : WSTRING[50] := "中文Unicode支持";
END_VAR

8.2 STRING 与 WSTRING 对比

特性STRINGWSTRING
编码ASCII / ANSI (单字节)Unicode (UTF-16, 每个字符 2 字节)
默认长度80 字符80 个宽字符
最大长度255 字符255 个宽字符
内存占用长度 + 1 字节(长度 + 1) × 2 字节
适用场景英文数字、短标签、IO 描述中文、日文、多语言文本
兼容性所有 PLC 通用部分老型号不支持

DarraRT 建议: 凡是涉及中文显示或国际化文本,统一使用 WSTRING;内部标签、报警编码、通信协议字段使用 STRING


9. 字符串操作函数

9.1 函数速查表

函数语法说明
LENLEN(s)返回字符串当前实际长度(不含结尾空字符)
LEFTLEFT(s, n)返回字符串最左边 n 个字符
RIGHTRIGHT(s, n)返回字符串最右边 n 个字符
MIDMID(s, start, n)返回从 start 开始的 n 个字符
CONCATCONCAT(s1, s2)拼接两个字符串
FINDFIND(s1, s2)返回 s2 在 s1 中首次出现的位置,未找到返回 0
INSERTINSERT(s1, s2, pos)在 s1 的 pos 位置插入 s2
DELETEDELETE(s, start, n)删除 s 中从 start 开始的 n 个字符
REPLACEREPLACE(s1, s2, start, n)将 s1 中从 start 开始的 n 个字符替换为 s2

9.2 使用示例

PROGRAM StringDemo
VAR
s, s1, s2, result : STRING[100];
pos, len : INT;
END_VAR

s := 'DarraRT PLC';

// LEN
len := LEN(s); // 10

// LEFT / RIGHT
result := LEFT(s, 5); // 'Darra'
result := RIGHT(s, 3); // 'PLC'

// MID
result := MID(s, 1, 4); // 'Darr' (从位置 1 开始取 4 个)

// CONCAT
s1 := 'Hello';
s2 := ' World';
result := CONCAT(s1, s2); // 'Hello World'

// FIND
pos := FIND(s, 'RT'); // 6 (位置从 1 开始计数)
pos := FIND(s, 'XYZ'); // 0 (未找到)

// INSERT
result := INSERT('Hello', ' AB', 6); // 'Hello AB' (在位置 6 插入, 即末尾)

// DELETE
result := DELETE('Hello World', 6, 6); // 'Hello' (删除位置 6 开始的 6 个字符)

// REPLACE
result := REPLACE('Hello World', 'PLC', 7, 5); // 'Hello PLC' (替换位置 7 开始的 5 个字符)
END_PROGRAM

9.3 常见陷阱

陷阱说明
索引从 1 开始字符串位置从 1 开始计数,不是 0
超出长度截断结果超出目标变量最大长度时自动截断,无警告
FIND 返回 0 表示未找到0 不是有效位置,位置从 1 开始
WSTRING 函数兼容部分函数不支持 WSTRING,需用 WSTRING 专用版本
空字符串处理LEN('') = 0,LEFT('', n) = '',FIND('', 'x') = 0

10. 字符串格式化与转换

10.1 数值转字符串

VAR
realVal : REAL := 123.456;
intVal : DINT := -789;
strReal : STRING[20];
strInt : STRING[20];
strResult : STRING[50];
END_VAR

// REAL → STRING
strReal := REAL_TO_STRING(realVal); // '1.234560e+002'

// DINT → STRING
strInt := DINT_TO_STRING(intVal); // '-789'

// 字符串拼接
strResult := CONCAT('Value = ', strReal);
END_PROGRAM

10.2 字符串转数值

VAR
s : STRING[20] := '123.456';
r : REAL;
di : DINT;
err : BOOL;
END_VAR

// STRING → REAL
r := STRING_TO_REAL(s); // 123.456

// STRING → DINT (会截断小数)
di := STRING_TO_DINT(s); // 123

// 非法字符串转换: 返回 0 (不抛异常)
s := 'ABC';
r := STRING_TO_REAL(s); // 0.0
// 建议先验证字符串格式
END_PROGRAM

10.3 类型转换函数表

转换方向函数示例结果
REAL → STRINGREAL_TO_STRING(1.5)'1.500000e+000'
LREAL → STRINGLREAL_TO_STRING(1.5)'1.500000000000000e+000'
DINT → STRINGDINT_TO_STRING(-123)'-123'
INT → STRINGINT_TO_STRING(99)'99'
TIME → STRINGTIME_TO_STRING(T#5s)'T#5s'
DATE → STRINGDATE_TO_STRING(D#2026-07-26)'D#2026-07-26'
STRING → REALSTRING_TO_REAL('1.5')1.5
STRING → DINTSTRING_TO_DINT('123')123
STRING → TIMESTRING_TO_TIME('T#5s')T#5s

11. 字符串陷阱与工业现场最佳实践

11.1 截断问题

VAR
sSmall : STRING[10] := 'Hello';
sLong : STRING[20] := 'This is a long message';
BEGIN

// 截断: 目标变量不足以容纳源字符串
sSmall := sLong; // sSmall = 'This is a ' (长度 10, 自动截断)

// 截断发生后无错误标志, 数据静默丢失
// 工业现场: 报警消息截断可能导致关键信息丢失!
END_VAR

最佳实践:

  • 字符串目标变量长度应 ≥ 源字符串实际最大长度。
  • 对重要的报警消息,使用 WSTRING[255]STRING[255] 避免截断。
  • 通信协议中的字符串字段必须严格按规范长度声明。

11.2 性能考虑

// 反模式: 在循环内反复拼接字符串 (性能差)
FOR i := 1 TO 100 DO
result := CONCAT(result, DINT_TO_STRING(i)); // 每次分配新字符串
END_FOR;

// 推荐: 预分配 + 直接写入数组
VAR
buf : ARRAY[0..255] OF CHAR; // 字符数组
idx : INT := 0;
END_VAR
FOR i := 1 TO 100 DO
// 直接写入 buf, 避免反复分配
idx := WriteIntToBuf(buf, idx, i);
END_FOR;

字符串性能准则:

  • 避免在高速循环(扫描周期 < 1ms)内做字符串拼接或格式化。
  • 大量字符串操作调度到慢速任务(如 100ms 周期)。
  • 固定格式使用 REAL_TO_STRING 等标准函数,不手写格式化。
  • 使用 ARRAY OF CHAR 替代字符串做底层缓冲区操作。

11.3 编码与国际化

VAR
// 中文用 WSTRING
wsChinese : WSTRING[50] := "温度传感器 #1 异常";

// 英文等 ASCII 用 STRING
sTag : STRING[30] := 'Temp_Sensor_01';

// 混用时的转换
wsConverted : WSTRING[50] := STRING_TO_WSTRING(sTag);
sConverted : STRING[50] := WSTRING_TO_STRING(wsChinese); // 中文可能丢失!
END_VAR

警告: WSTRING_TO_STRING 将 Unicode 转为单字节 ANSI 时,非 ASCII 字符(中文、日文等)会丢失。需要保留多语言文本时,全程使用 WSTRING


12. 数组处理模式

12.1 查找

FUNCTION FindFirst : INT
VAR_INPUT
arr : ARRAY[*] OF INT;
target : INT;
END_VAR
VAR
i : DINT;
BEGIN
FOR i := LOWER_BOUND(arr, 1) TO UPPER_BOUND(arr, 1) DO
IF arr[i] = target THEN
FindFirst := i;
RETURN;
END_IF;
END_FOR;
FindFirst := -1; // 未找到
END_FUNCTION

12.2 排序 (冒泡)

FUNCTION SortArray : BOOL
VAR_INPUT
arr : ARRAY[*] OF INT;
END_VAR
VAR
i, j : DINT;
n : DINT;
temp : INT;
BEGIN
n := UPPER_BOUND(arr, 1) - LOWER_BOUND(arr, 1) + 1;
FOR i := 0 TO n - 2 DO
FOR j := 0 TO n - 2 - i DO
IF arr[LOWER_BOUND(arr, 1) + j] > arr[LOWER_BOUND(arr, 1) + j + 1] THEN
temp := arr[LOWER_BOUND(arr, 1) + j];
arr[LOWER_BOUND(arr, 1) + j] := arr[LOWER_BOUND(arr, 1) + j + 1];
arr[LOWER_BOUND(arr, 1) + j + 1] := temp;
END_IF;
END_FOR;
END_FOR;
SortArray := TRUE;
END_FUNCTION

12.3 过滤

FUNCTION FilterPositive : INT
VAR_INPUT
src : ARRAY[*] OF INT;
dst : ARRAY[*] OF INT;
END_VAR
VAR
i, j : DINT;
low, high : DINT;
BEGIN
low := LOWER_BOUND(src, 1);
high := UPPER_BOUND(src, 1);
j := 0;
FOR i := low TO high DO
IF src[i] > 0 THEN
dst[j] := src[i];
j := j + 1;
END_IF;
END_FOR;
FilterPositive := j; // 返回有效元素个数
END_FUNCTION

12.4 归约 (求和)

FUNCTION SumArray : REAL
VAR_INPUT
arr : ARRAY[*] OF REAL;
END_VAR
VAR
i : DINT;
s : REAL;
BEGIN
s := 0.0;
FOR i := LOWER_BOUND(arr, 1) TO UPPER_BOUND(arr, 1) DO
s := s + arr[i];
END_FOR;
SumArray := s;
END_FUNCTION

13. 内存布局考虑

13.1 数组内存布局

ARRAY[1..3, 1..4] OF INT  (12 × 2 = 24 字节)

行优先存储:
地址偏移 元素
0 [1,1]
2 [1,2]
4 [1,3]
6 [1,4]
8 [2,1]
10 [2,2]
... ...
22 [3,4]

13.2 字符串内存布局

STRING[10]:
字节 0: 当前长度 (BYTE)
字节 1-10: 字符数据 (CHAR[10])
总计: 11 字节

WSTRING[10]:
字节 0-1: 当前长度 (INT)
字节 2-21: 宽字符数据 (WCHAR[10] = 2 字节 × 10)
总计: 22 字节

13.3 内存对齐与优化

VAR
// 良好: 相同类型连续声明
buf1 : ARRAY[0..127] OF INT;
buf2 : ARRAY[0..127] OF INT;

// 较差: 不同类型交错导致填充字节
arr1 : ARRAY[0..9] OF INT; // 20 字节
b1 : BOOL; // 1 字节 + 3 填充
arr2 : ARRAY[0..9] OF INT; // 20 字节
END_VAR

内存优化建议:

  1. 大数组放在 VAR 块顶部,避免中间插入小类型变量导致对齐填充。
  2. 通信缓冲区使用 ARRAY[0..N] OF BYTE 而非 STRING[N],避免长度字节干扰。
  3. 全局 DB 中的数组尽量放在连续地址区域,方便过程映像映射。
  4. 使用 UNIONAT 覆盖实现在同一内存区域复用不同类型。
  5. 字符串数组 ARRAY[0..N] OF STRING[M] 占用 (N+1) × (M+1) 字节,注意总内存预算。

14. 总结

主题要点
ARRAY 声明指定上下界,元素个数 = 上界 - 下界 + 1
初始化字面量 [...],重复语法 [N(value)]
索引零基用于缓冲区,一基用于物理设备;运行时检查边界
多维数组行优先,最右侧下标变化最快
切片DarraRT 扩展,长度必须匹配
整数组赋值类型、大小完全一致才允许
ARRAY[*]仅限函数参数,用 LOWER_BOUND / UPPER_BOUND 取边界
STRING默认 80 字符,最大 255;中文用 WSTRING
字符串函数位置从 1 开始,FIND 未找到返回 0
格式化转换REAL_TO_STRING / DINT_TO_STRING 等;非法输入返回 0
截断自动截断不报错,目标长度必须足够
性能高速循环内避免字符串操作;大数组注意内存布局