Day 02 - 运算符与表达式
目标:掌握ArkTS运算符体系,重点理解与C++的差异和ArkTS特有限制
预计时间:2-3小时
第一部分:算术运算符
1.1 四则运算(+、-、*、/、%)
C++ 的四则运算在 ArkTS 中基本保持一致,但有一个关键差异:除法行为。
语法:
表达式1 运算符 表达式2
代码示例:
// IoT 场景:传感器数据计算
let voltage: number = 3.3; // 电压(V)
let resistance: number = 1000; // 电阻(Ω)
let current: number = voltage / resistance; // 电流(A)
console.log(current.toString()); // "0.0033"
let totalSamples: number = 1000; // 总采样数
let validSamples: number = 976; // 有效采样数
let remainder: number = totalSamples % validSamples; // 取余
console.log(remainder.toString()); // "24"
⚠️ 重要差异:除法总是返回浮点数
let a: number = 7;
let b: number = 2;
let result: number = a / b;
console.log(result.toString()); // "3.5"(不是"3"!)
对比 C++:
int a = 7;
int b = 2;
int result = a / b; // C++:整数除法,结果为 3(截断)
double r2 = 7.0 / 2; // C++:需要显式用浮点数才得到 3.5
// ArkTS 只有 number(等价于 double),所以 7 / 2 = 3.5
ArkTS 中如何取整除结果? 使用 Math.floor() 或 Math.trunc():
let packets: number = 10;
let packetSize: number = 3;
// 等价 C++ 整数除法(截断)
let fullPackets: number = Math.trunc(packets / packetSize);
console.log(fullPackets.toString()); // "3"
// 向下取整(负数时与 trunc 不同)
let floorResult: number = Math.floor(-7 / 2); // -4(向负无穷)
let truncResult: number = Math.trunc(-7 / 2); // -3(向零截断)
1.2 字符串拼接 +
ArkTS 中 + 运算符在字符串语境下用于拼接。当 + 的任一操作数为 string 时,另一个操作数会调用 toString() 转为字符串后拼接。
// IoT 场景:构建设备日志信息
let deviceId: string = "SENSOR_001";
let status: string = "ONLINE";
let logEntry: string = deviceId + " -> " + status;
console.log(logEntry); // "SENSOR_001 -> ONLINE"
// 只要有 string 参与,其他类型自动转字符串
let count: number = 42;
let active: boolean = true;
let msg1: string = "Count: " + count; // "Count: 42"
let msg2: string = "Active: " + active; // "Active: true"
let msg3: string = "Status: " + null; // "Status: null"
// 注意:数字在前会先进行数学运算
let r1: string = 1 + 2 + " items"; // "3 items"(先算 1+2)
let r2: string = "items: " + 1 + 2; // "items: 12"(先拼接 "items: 1")
对比 C++:
// C++ 不允许 int + string
int count = 42;
std::string label = "packets";
// std::string msg = count + label; // ❌ C++ 编译错误
// C++ 需要显式转换
std::string msg = std::to_string(count) + " " + label;
// ArkTS 自动转换,但推荐模板字符串更清晰
let msg: string = `${count} ${label}`;
1.3 幂运算 **
ArkTS 支持 ** 运算符,对应 C++ 的 pow() 函数。
// IoT 场景:计算信号衰减(dB 计算)
let base: number = 2;
let exponent: number = 10;
let result: number = base ** exponent; // 2^10
console.log(result.toString()); // "1024"
// 计算采样率(MHz)
let sampleRate: number = 10 ** 6; // 10^6 = 1,000,000 Hz
console.log(sampleRate.toString()); // "1000000"
对比 C++:
#include <cmath>
double result = pow(2, 10); // C++:需要 pow() 函数
let result: number = 2 ** 10; // ArkTS:内置运算符,更简洁
1.4 自增自减 ++/--
与 C++ 完全一致,支持前缀和后缀两种形式。
// IoT 场景:设备计数器
let packetCount: number = 0;
packetCount++; // 后缀自增:先用值,再加1
console.log(packetCount.toString()); // "1"
packetCount++; // 前缀自增:先加1,再用值
console.log(packetCount.toString()); // "2"
let seq: number = 5;
let a: number = seq++; // a = 5,seq = 6(后缀:先赋值,后自增)
let b: number = ++seq; // b = 7,seq = 7(前缀:先自增,后赋值)
console.log(a.toString(), b.toString(), seq.toString()); // "5" "7" "7"
对比 C++: 行为完全相同。
1.5 一元运算符的ArkTS限制(+/-)
⚠️ 这是与C++的重大差异!
在 C++ 中,一元 +、- 可以作用于任何整数;ArkTS 严格禁止对非数值类型使用一元运算符。
注意:~ 按位取反属于位运算符,见第五部分。
let voltage: number = 3.3;
let negVoltage: number = -voltage; // ✅ -3.3,数值取负
let posVoltage: number = +voltage; // ✅ 3.3,一元加(无实际效果)
// ❌ 编译错误:一元 + 不能用于 string
let strVal: string = "100";
// let numVal: number = +strVal; // ❌ 编译错误!
// ✅ 正确:显式转换
let numVal: number = Number(strVal);
console.log(numVal.toString()); // "100"
对比 C++:
// C++ 允许隐式转换(不推荐)
std::string s = "100";
// 注意:C++ 中 +"100" 也不合法,但 C++ 字符串转数字可以用 atoi
// ArkTS 明确禁止对字符串使用一元 +/-
// 必须使用 Number() 显式转换
let n: number = Number("100"); // ✅
【第一部分小结】
| 运算符 | C++ 行为 | ArkTS 行为 | 注意 |
|---|---|---|---|
+ - * % |
整数/浮点均支持 | 仅数值,无整数/浮点区分 | — |
/ |
整数/整数 = 整数(截断) | 总返回浮点数 | ⚠️ 差异 |
+(字符串) |
std::string 可用 |
string + 任意类型,自动转字符串 | ⚠️ 差异 |
** |
需要 pow() |
内置 ** 运算符 |
ArkTS增强 |
++ -- |
前/后缀均支持 | 完全相同 | — |
一元 +/- |
支持隐式类型转换 | 仅限数值类型,字符串编译报错 | ⚠️ 差异 |
~ |
位取反 | 位取反(见第五部分) | 属于位运算符 |
第二部分:比较运算符
2.1 严格相等 === 和 !==(始终使用这两个)
ArkTS 最佳实践:始终使用 === 和 !==,不要使用 == 和 !=。
=== 同时比较值和类型,只有两者都相同才返回 true。
// IoT 场景:设备状态检测
let currentTemp: number = 85.0;
let threshold: number = 85.0;
let deviceId: string = "001";
let targetId: string = "001";
console.log((currentTemp === threshold).toString()); // "true"(值和类型都相同)
console.log((deviceId === targetId).toString()); // "true"
// ❌ 编译错误:number 和 string 类型无交集,不能用 === 比较
// console.log((currentTemp === deviceId).toString());
// 不等于
let errorCode: number = 404;
console.log((errorCode !== 200).toString()); // "true"
console.log((errorCode !== 404).toString()); // "false"
对比 C++:
int a = 5;
int b = 5;
if (a == b) { ... } // C++ 的 == 只有一种,强类型编译期保证类型
// ArkTS 的 === 在行为上类似 C++ 的 ==(因为类型由编译器保证)
2.2 为什么不推荐 == 和 !=
== 是"宽松相等",进行比较前会尝试类型转换,规则复杂且容易出错。
// == 的怪异行为示例(了解即可,不要使用)
// 注意:以下示例仅为说明行为,实际代码中不要使用 ==
// console.log((0 == false).toString()); // "true"(隐式转换:false → 0)
// console.log(("" == false).toString()); // "true"
// console.log((null == undefined).toString()); // "true"
// === 行为清晰可预期
// console.log((0 === false).toString()); // "false"(类型不同)
// console.log(("" === false).toString()); // "false"
// console.log((null === undefined).toString()); // "false"
⚠️ 重要区别:null == undefined 为 true,但 null === undefined 为 false
这是 == 和 === 最实际的区别。在 ArkTS 中:
- 用
===时,null和undefined不相等 - 要同时判断两者,需要显式判断:
val === null || val === undefined - 或用
??:val ?? defaultValue(对 null 和 undefined 都生效)
联合类型的比较规则:
// 联合类型与具体类型比较,类型有交集即可用 ===
function check(value: string | number): void {
// value 是 string | number,与 string "42" 有交集(string 部分)
if (value === "42") { // ✅ 编译通过,只比较 string 部分
console.log("string 42");
}
// 与 number 42 也有交集
if (value === 42) { // ✅ 编译通过,只比较 number 部分
console.log("number 42");
}
}
// 局部变量中,不同类型用 === 会编译错误(无交集)
let a: number = 42;
let b: string = "42";
// console.log(a === b); // ❌ 编译错误:number 和 string 无交集
结论: ArkTS 开发中统一使用 === 和 !==,代码行为更可预测,符合强类型语言习惯。
2.3 大小比较(>、<、>=、<=)
// IoT 场景:温度报警阈值检查
let temperature: number = 92.5;
let warningThreshold: number = 85.0;
let criticalThreshold: number = 95.0;
let isWarning: boolean = temperature > warningThreshold;
let isCritical: boolean = temperature >= criticalThreshold;
let isNormal: boolean = temperature < warningThreshold;
console.log(isWarning.toString()); // "true"
console.log(isCritical.toString()); // "false"
console.log(isNormal.toString()); // "false"
// 字符串也可以比较(字典序)
let id1: string = "SENSOR_001";
let id2: string = "SENSOR_002";
console.log(id1 < id2); // true(字典序比较)
对比 C++: 行为完全一致。
2.4 特殊值比较(NaN、+0/-0、引用比较)
⚠️ 三个特殊情况需要特别记忆:
① NaN 与任何值(包括自身)比较都返回 false
let invalidReading: number = NaN; // 传感器读数无效
// console.log((invalidReading === NaN).toString()); // "false"(NaN不等于NaN)
// console.log((invalidReading !== NaN).toString()); // "true"
// ✅ 检查 NaN 的正确方式
let isInvalid: boolean = Number.isNaN(invalidReading);
console.log(isInvalid.toString()); // "true"
② +0 和 -0 用 === 比较返回 true
let pos: number = +0;
let neg: number = -0;
// console.log((pos === neg).toString()); // "true"(=== 认为相等)
// 如需区分 +0/-0,使用 Object.is()
// console.log(Object.is(pos, neg).toString()); // "false"
③ 引用类型比较的是地址,不是内容
// IoT 场景:设备配置对象比较
let config1: number[] = [1, 2, 3];
let config2: number[] = [1, 2, 3];
let config3: number[] = config1; // 指向同一对象
// console.log((config1 === config2).toString()); // "false"(内容相同但地址不同)
// console.log((config1 === config3).toString()); // "true"(同一地址)
对比 C++:
// C++ 指针比较(类似引用比较)
int arr1[] = {1, 2, 3};
int arr2[] = {1, 2, 3};
int* p1 = arr1;
int* p2 = arr2;
std::cout << (p1 == p2); // false,地址不同
// ArkTS 数组是引用类型,=== 比较地址,同 C++ 指针
【第二部分小结】
| 运算符 | 说明 | C++ 对应 | 注意 |
|---|---|---|---|
=== |
严格相等(值+类型) | ==(强类型语境下等价) |
✅ 始终使用 |
!== |
严格不等 | != |
✅ 始终使用 |
== |
宽松相等(有隐式转换) | — | ❌ 不推荐 |
!= |
宽松不等 | — | ❌ 不推荐 |
> < >= <= |
大小比较 | 完全相同 | — |
NaN === NaN |
返回 false |
— | ⚠️ 用 Number.isNaN() |
+0 === -0 |
返回 true |
— | ⚠️ 用 Object.is() 区分 |
数组/对象 === |
比较引用地址 | 指针比较 | ⚠️ 不比较内容 |
第三部分:逻辑运算符
3.1 基本逻辑(&&、||、!)
与 C++ 行为一致,但返回值语义在 ArkTS 中有实际应用。
// IoT 场景:多条件设备状态判断
let isConnected: boolean = true;
let hasPower: boolean = true;
let hasError: boolean = false;
// && 逻辑与:全为 true 才为 true
let isReady: boolean = isConnected && hasPower && !hasError;
console.log(isReady.toString()); // "true"
// || 逻辑或:任一为 true 即为 true
let needsAttention: boolean = hasError || !isConnected;
console.log(needsAttention.toString()); // "false"
// ! 逻辑非
let isOffline: boolean = !isConnected;
console.log(isOffline.toString()); // "false"
3.2 短路求值及实际应用
&& 短路:左侧为 false,右侧不执行
|| 短路:左侧为 true,右侧不执行
这在 ArkTS 中有实际应用价值(条件执行和默认值):
// IoT 场景:条件执行(利用 && 短路)
let sensor: string | null = "SENSOR_001";
// 利用 && 短路:只有 sensor 非 null 时才访问属性
let length: number = (sensor !== null) && sensor.length || 0;
// 等价于:if (sensor !== null) { length = sensor.length }
// 更 ArkTS 化的写法(用 ?. 和 ??,见第六部分)
// 利用 || 短路:提供默认值(注意:0 和 "" 也会触发默认值)
let deviceName: string = "" || "未命名设备";
console.log(deviceName); // "未命名设备"(因为 "" 是 falsy)
let packetRate: number = 0 || 1000;
console.log(packetRate.toString()); // "1000"(因为 0 是 falsy!可能不是你要的)
// ⚠️ 当 0 是合法值时,用 ?? 而不是 ||(见第六部分)
对比 C++:
// C++ 也有短路求值,概念相同
bool isReady = isConnected && hasPower;
// C++ 中常用短路来避免空指针
if (ptr != nullptr && ptr->isValid()) { ... }
// ArkTS 有 ?. 可选链更优雅地处理此类情况
【第三部分小结】
| 运算符 | 说明 | C++ 对应 | 注意 |
|---|---|---|---|
&& |
逻辑与,短路求值 | && |
左假则短路 |
|| |
逻辑或,短路求值 | || |
左真则短路 |
! |
逻辑非 | ! |
— |
|| 默认值 |
0、"" 也触发默认 |
— | ⚠️ 合法零值用 ?? |
第四部分:位运算符
4.1 基本位运算(&、|、^、~、<<、>>、>>>)
ArkTS 的位运算符与 C++ 基本一致,ArkTS 额外支持 >>> 无符号右移(C++ 无直接对应)。
⚠️ 重要:ArkTS 的 number 是 64 位浮点数,位运算会先将其转换为 32 位整数,运算完再转回。
// IoT 场景:协议字节解析
let statusByte: number = 0b1010_1100; // 设备状态字节(8位)
// & 按位与:提取特定位
let bit7: number = statusByte & 0b1000_0000; // 提取最高位(报警标志)
let lowNibble: number = statusByte & 0x0F; // 提取低4位
// | 按位或:设置特定位
let withFlag: number = statusByte | 0b0000_0001; // 设置第0位
// ^ 按位异或:翻转特定位
let toggled: number = statusByte ^ 0b0000_0100; // 翻转第2位
// ~ 按位取反(注意:32位翻转)
let inverted: number = ~statusByte;
console.log(inverted.toString()); // "-173"(因为按32位取反)
// << 左移(相当于 × 2^n)
let shifted: number = 1 << 4; // 1 × 2^4 = 16
console.log(shifted.toString()); // "16"
// >> 有符号右移(保留符号位,相当于 ÷ 2^n 向下取整)
let signedShift: number = -16 >> 2; // -4
console.log(signedShift.toString()); // "-4"
// >>> 无符号右移(高位补0,不保留符号位)
let unsignedShift: number = -1 >>> 28; // 高位补0
console.log(unsignedShift.toString()); // "15"
对比 C++:
// C++ 位运算
unsigned char statusByte = 0b10101100;
unsigned char bit7 = statusByte & 0b10000000;
unsigned char toggled = statusByte ^ 0b00000100;
int shifted = 1 << 4; // 16
// C++ 没有 >>>(无符号右移),需要用 unsigned 类型的 >> 实现
unsigned int val = (unsigned int)(-1) >> 28; // 等价于 ArkTS 的 >>>
4.2 实战:权限标志系统
C++ 开发者最熟悉的位运算应用场景!
// IoT 场景:设备权限控制系统
// 权限标志位定义(每个 bit 代表一种权限)
const PERM_READ: number = 1 << 0; // 0b0000_0001 = 1
const PERM_WRITE: number = 1 << 1; // 0b0000_0010 = 2
const PERM_EXEC: number = 1 << 2; // 0b0000_0100 = 4
const PERM_CONFIG: number = 1 << 3; // 0b0000_1000 = 8
const PERM_ADMIN: number = 1 << 7; // 0b1000_0000 = 128
// 初始化用户权限(只读)
let userPermissions: number = PERM_READ;
// 添加权限(按位或)
function addPermission(perm: number, flag: number): number {
return perm | flag;
}
// 撤销权限(按位与 + 取反)
function removePermission(perm: number, flag: number): number {
return perm & ~flag;
}
// 检查是否有某权限(按位与 + 非零判断)
function hasPermission(perm: number, flag: number): boolean {
return (perm & flag) !== 0;
}
// 切换权限(异或)
function togglePermission(perm: number, flag: number): number {
return perm ^ flag;
}
// 使用示例
let devicePerm: number = PERM_READ;
devicePerm = addPermission(devicePerm, PERM_WRITE); // 加写权限
devicePerm = addPermission(devicePerm, PERM_CONFIG); // 加配置权限
console.log(hasPermission(devicePerm, PERM_READ).toString()); // "true"
console.log(hasPermission(devicePerm, PERM_WRITE).toString()); // "true"
console.log(hasPermission(devicePerm, PERM_ADMIN).toString()); // "false"
devicePerm = removePermission(devicePerm, PERM_WRITE); // 撤销写权限
console.log(hasPermission(devicePerm, PERM_WRITE).toString()); // "false"
// 打印权限值
console.log(devicePerm.toString(2)); // "1001"(READ + CONFIG)
对比 C++:
// C++ 版本(逻辑完全相同)
const int PERM_READ = 1 << 0;
const int PERM_WRITE = 1 << 1;
const int PERM_CONFIG = 1 << 3;
int perm = PERM_READ;
perm |= PERM_WRITE; // 添加权限
perm &= ~PERM_WRITE; // 撤销权限
bool hasWrite = (perm & PERM_WRITE) != 0; // 检查权限
// ArkTS 写法与 C++ 几乎一模一样
【第四部分小结】
| 运算符 | 说明 | C++ 对应 | 注意 |
|---|---|---|---|
& |
按位与 | & |
相同 |
| |
按位或 | | |
相同 |
^ |
按位异或 | ^ |
相同 |
~ |
按位取反 | ~ |
⚠️ 32位整数取反 |
<< |
左移 | << |
相同 |
>> |
有符号右移 | >>(有符号类型) |
相同 |
>>> |
无符号右移 | 无对应(需用 unsigned 类型) |
ArkTS增强 |
第五部分:赋值运算符
5.1 基本赋值与复合赋值(+=、-=、*=、/=、%=、**=)
// IoT 场景:累加传感器数据
let totalVoltage: number = 0.0;
let sampleCount: number = 0;
// 模拟采集5次数据
totalVoltage += 3.28; // totalVoltage = 0 + 3.28 = 3.28
totalVoltage += 3.31; // totalVoltage = 3.28 + 3.31 = 6.59
totalVoltage += 3.30; // = 9.89
totalVoltage += 3.29; // = 13.18
totalVoltage += 3.32; // = 16.50
sampleCount += 5; // sampleCount = 5
let avgVoltage: number = totalVoltage / sampleCount;
console.log(avgVoltage.toFixed(3)); // "3.300"
// 其他复合赋值
let gain: number = 2.0;
gain *= 1.5; // gain = 3.0(放大倍数)
gain **= 2; // gain = 9.0(平方)
gain /= 3; // gain = 3.0
gain %= 2; // gain = 1.0
gain -= 0.5; // gain = 0.5
console.log(gain); // 0.5
对比 C++: 行为完全一致。
5.2 位运算复合赋值(&=、|=、^=、<<=、>>=)
基于第四部分的位运算符,复合赋值简化代码:
// IoT 场景:设备权限标志管理
let permissions: number = 0b0000_0000; // 8位权限标志
// 设置第0位(读权限)
permissions |= 0b0000_0001; // permissions = 0b0000_0001
// 设置第1位(写权限)
permissions |= 0b0000_0010; // permissions = 0b0000_0011
// 设置第2位(执行权限)
permissions |= 0b0000_0100; // permissions = 0b0000_0111
console.log(permissions.toString(2)); // "111"
// 清除第1位(移除写权限)
permissions &= ~0b0000_0010; // permissions = 0b0000_0101
console.log(permissions.toString(2)); // "101"
// 切换第3位(翻转管理员权限)
permissions ^= 0b0000_1000; // permissions = 0b0000_1101
console.log(permissions.toString(2)); // "1101"
// 左移/右移赋值
let data: number = 0b0001;
data <<= 3; // data = 0b1000(左移3位,相当于 * 8)
data >>= 1; // data = 0b0100(右移1位,相当于 / 2)
console.log(data.toString(2)); // "100"
对比 C++: 行为完全一致。
【第五部分小结】
| 运算符 | 等价于 | C++ 对应 | 注意 |
|---|---|---|---|
+= |
a = a + b |
相同 | — |
-= |
a = a - b |
相同 | — |
*= |
a = a * b |
相同 | — |
/= |
a = a / b |
相同 | ⚠️ 结果为浮点 |
%= |
a = a % b |
相同 | — |
**= |
a = a ** b |
无对应,C++需 pow() |
ArkTS增强 |
&= |= ^= |
位运算赋值 | 相同 | 基于第四部分的位运算符 |
<<= >>= |
移位赋值 | 相同 | 基于第四部分的位运算符 |
第六部分:条件与空值处理运算符(ArkTS 重点)
6.1 三元运算符 ?:
与 C++ 完全一致。
// IoT 场景:根据温度决定设备状态描述
let temperature: number = 92.5;
let warningLevel: string = temperature > 90.0 ? "高温警告" : "正常";
console.log(warningLevel); // "高温警告"
// 三元运算符可以嵌套(不推荐过多嵌套)
let alertLevel: string =
temperature > 100 ? "紧急" :
temperature > 90 ? "警告" :
temperature > 80 ? "注意" : "正常";
console.log(alertLevel); // "警告"
对比 C++: 语法和行为完全相同。
6.2 空值合并 ??(ArkTS 空安全核心特性)
?? 是 ArkTS 中处理 null/undefined 的专用运算符,只有左侧为 null 或 undefined 时才返回右侧值。
这与 || 的关键区别:|| 对 0、"" 也返回右侧,?? 不会!
// IoT 场景:传感器读数可能为 null(未连接)
let rawReading: number | null = null; // 传感器未连接
// ✅ ?? 只对 null/undefined 生效
let reading: number = rawReading ?? -1; // -1 表示无效值
console.log(reading); // -1
// 当传感器有值时
let rawReading2: number | null = 0; // 传感器读到 0(合法值!)
let reading2: number = rawReading2 ?? -1;
console.log(reading2); // 0(不会替换为-1!)
// ⚠️ 对比 ||:会把 0 也替换掉
let reading3: number = (rawReading2 as number) || -1;
console.log(reading3); // -1(错误!0 是合法值,被错误替换了)
等价三元写法:
// ?? 完全等价于以下三元运算符
let val: number | null = null;
// ?? 写法
let result: number = val ?? 100;
// 等价三元写法
let result2: number = (val !== null && val !== undefined) ? val : 100;
// 两者完全等价,?? 更简洁
⚠️ ?? 与 || 混用限制(必须加括号):
// ❌ 编译错误:?? 和 || 不能直接混用,优先级歧义
// let v = a || b ?? c;
// ✅ 正确:加括号明确优先级
let a: number | null = null;
let b: number | null = 5;
let c: number = 10;
let v: number = (a ?? 0) || (b ?? 0); // ✅ 加括号
console.log(v); // 5
6.3 可选链 ?.(简化多层嵌套访问)
?. 用于简化对可能为 null/undefined 的对象属性的访问,将多层 if 判断简化成链式访问。
注意:ArkTS 在编译期就能检查出 null 访问错误,?. 的主要作用是简化代码,不是防止运行时崩溃(C++ 才需要)。
// IoT 场景:设备配置可能未初始化
interface SensorConfig {
name: string;
calibration: {
offset: number;
scale: number;
} | null;
}
let config: SensorConfig | null = null;
// ❌ 不用 ?.,需要写很多判断
let offset1: number;
if (config !== null && config.calibration !== null) {
offset1 = config.calibration.offset;
} else {
offset1 = 0.0;
}
// ✅ 用 ?. 一行搞定
let offset2: number = config?.calibration?.offset ?? 0.0;
// 工作原理:
// config?.calibration?.offset
// 等价于:config !== null ? config.calibration?.offset : undefined
// 再展开:config !== null ? (config.calibration !== null ? config.calibration.offset : undefined) : undefined
?. 的工作机制:
// 左侧为 null 或 undefined 时,返回 undefined
let a: number | null = null;
console.log(a?.toString()); // undefined(不是崩溃)
// 左侧有值时,正常执行属性访问或方法调用
let b: number | null = 42;
console.log(b?.toString()); // "42"
对比 C++:
// C++ 没有 ?. 运算符,需要手动判空(否则运行时崩溃)
SensorConfig* config = nullptr;
// std::string name = config->name; // 💥 崩溃!
// C++ 必须显式判断
std::string name;
if (config != nullptr && config->calibration != nullptr) {
name = config->calibration->offset;
}
// ArkTS 的 ?. 把 C++ 的多层 if 简化成链式访问
let offset: number = config?.calibration?.offset ?? 0.0;
6.4 非空断言 !(编译期校验,运行时风险)
! 后缀运算符告诉编译器:"我确定这个值不是 null/undefined,请不要报类型错误。"
⚠️ 这只是告诉编译器,不做运行时检查!如果实际是 null,运行时会崩溃。
// IoT 场景:已知传感器一定已连接
function getSensorReading(): number | null {
return 25.6; // 实际上一定有值
}
let rawValue: number | null = getSensorReading();
// 没有非空断言时,number | null 不能直接用于数学运算
// let doubled = rawValue * 2; // ❌ 编译错误:rawValue 可能为 null
// ✅ 非空断言:告诉编译器 rawValue 一定不是 null
let doubled: number = rawValue! * 2;
console.log(doubled); // 51.2
// ✅ 更安全的做法:先判断
if (rawValue !== null) {
let safeDoubled: number = rawValue * 2; // 类型收窄为 number
console.log(safeDoubled);
}
非空断言 vs 可选链:
let sensor: string | null = null;
// ! 非空断言:坚信不为 null,如果是 null 则运行时崩溃
// let len1: number = sensor!.length; // 运行时崩溃!
// ?. 可选链:安全访问,null 时返回 undefined
let len2: number | undefined = sensor?.length; // undefined(安全)
6.5 typeof 运算符
typeof 用于运行时获取变量的类型字符串,常用于类型守卫。
⚠️ ArkTS 限制:typeof 只能用在表达式中,不能用作类型注解!
// ✅ 正确:在表达式中使用 typeof
let voltage: number = 3.3;
let name: string = "Sensor";
let active: boolean = true;
console.log(typeof voltage); // "number"
console.log(typeof name); // "string"
console.log(typeof active); // "boolean"
console.log(typeof null); // "object"(历史遗留,注意!)
console.log(typeof undefined); // "undefined"
console.log(typeof [1,2,3]); // "object"
// ✅ 在函数中用 typeof 做类型守卫
function processValue(val: string | number): void {
if (typeof val === "string") {
console.log("字符串长度:", val.length);
} else {
console.log("数值平方:", val * val);
}
}
❌ ArkTS 不允许将 typeof 用作类型注解:
let n1: number = 42;
// ❌ 编译错误:typeof 不能用于类型位置
// let n2: typeof n1 = 100; // ArkTS 编译错误!
// ✅ 正确做法:直接写类型
let n2: number = 100;
对比 C++:
// C++ 的 typeid(运行时类型信息)
#include <typeinfo>
int n = 42;
std::cout << typeid(n).name(); // "i"(平台相关,不友好)
// C++ 还有 decltype(编译期类型)
decltype(n) m = 100; // m 的类型与 n 相同(int)
// ArkTS 禁止类似的 typeof 类型用法
typeof 返回值速查表:
| 值 | typeof 返回 |
|---|---|
number 值 |
"number" |
string 值 |
"string" |
boolean 值 |
"boolean" |
undefined |
"undefined" |
null |
"object" ⚠️ |
| 数组 | "object" |
| 函数 | "function" |
6.6 instanceof 运算符(部分支持)
instanceof 检查一个对象是否是某个类的实例,左操作数必须是引用类型。
// IoT 场景:设备类型检查
class Sensor {
name: string = "Generic Sensor";
}
class TemperatureSensor extends Sensor {
temperature: number = 0;
}
class HumiditySensor extends Sensor {
humidity: number = 0;
}
let tempSensor: TemperatureSensor = new TemperatureSensor();
let humSensor: HumiditySensor = new HumiditySensor();
console.log(tempSensor instanceof TemperatureSensor); // true
console.log(tempSensor instanceof Sensor); // true(继承关系)
console.log(tempSensor instanceof HumiditySensor); // false
// 数组也可以用 instanceof
let readings: number[] = [25.1, 25.3, 25.2];
console.log(readings instanceof Array); // true
⚠️ instanceof 限制:左操作数必须是引用类型
let n: number = 42;
// ❌ 编译错误:基础类型(number/string/boolean)不能用 instanceof
// console.log(n instanceof Number); // ArkTS 编译错误!
// ✅ 只对对象、数组、类实例使用 instanceof
let arr: number[] = [1, 2, 3];
console.log(arr instanceof Array); // ✅ true
对比 C++:
// C++ 用 dynamic_cast 实现类似功能(需要多态类)
Sensor* sensor = new TemperatureSensor();
TemperatureSensor* ts = dynamic_cast<TemperatureSensor*>(sensor);
bool isTemp = (ts != nullptr); // true
// ArkTS 的 instanceof 更简洁
let isTemp: boolean = sensor instanceof TemperatureSensor;
【第六部分小结】
| 运算符 | 说明 | C++ 对应 | ArkTS 限制 |
|---|---|---|---|
?: |
三元条件 | 相同 | — |
?? |
空值合并(仅 null/undefined) | 无 | ⚠️ 与 || 混用需加括号 |
?. |
可选链(安全访问) | 无(需手动判空) | ⚠️ 不能用作左值/new/模板标签 |
!(后缀) |
非空断言 | 无 | ⚠️ 仅编译期,运行时不检查 |
typeof |
运行时类型字符串 | typeid |
⚠️ 不能用作类型注解 |
instanceof |
类型实例检查 | dynamic_cast |
⚠️ 左操作数须为引用类型 |
第七部分:ArkTS 不支持的运算符(重要提醒)
7.1 不支持 delete 运算符
原因: ArkTS 对象的属性布局在编译期已确定,不允许运行时动态删除属性。这是 ArkTS 相比 TypeScript 的重要约束,保证了内存安全和性能。
// ❌ 编译错误:ArkTS 不支持 delete
interface DeviceInfo {
id: string;
name: string;
}
let device: DeviceInfo = { id: "001", name: "Sensor" };
// delete device.name; // ❌ ArkTS 编译错误!
// ✅ ArkTS 替代方案:用联合类型 + undefined 处理可选属性
interface DeviceInfoOpt {
id: string;
name: string | undefined;
}
let device2: DeviceInfoOpt = { id: "001", name: "Sensor" };
device2.name = undefined; // ✅ 将属性设为 undefined 表示"不存在"
TypeScript 写法(错误)vs ArkTS 替代方案:
| TypeScript | ArkTS | |
|---|---|---|
| 删除对象属性 | delete obj.prop ✅ |
不支持 ❌ |
| 替代方案 | — | 属性设为 undefined ✅ |
7.2 不支持 in 运算符
原因: ArkTS 对象布局编译时已知,不需要运行时检查属性是否存在。
// ❌ 编译错误:ArkTS 不支持 in 运算符
let config: object = { timeout: 1000 };
// if ("timeout" in config) { ... } // ❌ ArkTS 编译错误!
// ✅ ArkTS 替代方案1:用 instanceof 检查类型
class SensorConfig {
timeout: number = 1000;
}
let cfg: SensorConfig = new SensorConfig();
if (cfg instanceof SensorConfig) {
console.log(cfg.timeout); // ✅
}
// ✅ ArkTS 替代方案2:用类型守卫(typeof)
function hasTimeout(val: SensorConfig | null): boolean {
return val !== null; // 如果类型正确,属性一定存在
}
// ✅ ArkTS 替代方案3:可选链 + ?? 安全访问
let timeout: number = cfg?.timeout ?? 0; // 安全访问
TypeScript 写法(错误)vs ArkTS 替代方案:
| TypeScript | ArkTS | |
|---|---|---|
| 检查属性存在 | "prop" in obj ✅ |
不支持 ❌ |
| 替代方案 | — | instanceof / 可选链 ✅ |
7.3 逗号运算符仅限 for 循环
ArkTS 中逗号运算符仅在 for 循环的初始化和迭代表达式中合法,其他场景编译报错。
// ✅ 合法:for 循环中使用逗号
for (let i: number = 0, j: number = 10; i < j; i++, j--) {
// i 递增,j 递减,双指针遍历
console.log(i, j);
}
// ❌ 编译错误:其他场景不能用逗号运算符
// let result = (1, 2, 3); // ❌ ArkTS 编译错误!
// ❌ 编译错误:函数调用中的逗号是参数分隔符,不是运算符,不会"求值"
// let val = doSomething(a, (b = 1, c = 2)); // ❌
// ✅ 替代方案:用多条语句代替
let a: number = 0;
let b: number = 0;
a = 1; // 第一个操作
b = 2; // 第二个操作
// 使用 b 的值
TypeScript 写法(错误)vs ArkTS 替代方案:
| TypeScript | ArkTS | |
|---|---|---|
| 逗号运算符 | (a = 1, b = 2) ✅ |
仅 for 循环 ❌ |
| 替代方案 | — | 拆分为独立语句 ✅ |
第八部分:运算符优先级速查
从高到低排列,数字越小优先级越高:
| 优先级 | 运算符 | 说明 | ArkTS特有 |
|---|---|---|---|
| 1 | () |
圆括号(分组) | — |
| 2 | x! x?.y x?.() |
非空断言、可选链 | ✅ ArkTS特有 |
| 3 | ++x --x +x -x ~ ! |
前缀一元运算符 | — |
| 4 | ** |
幂运算 | — |
| 5 | * / % |
乘法、除法、取余 | — |
| 6 | + - |
加法、减法、字符串拼接 | — |
| 7 | << >> >>> |
位移运算 | >>> ArkTS支持 |
| 8 | < <= > >= instanceof |
关系运算 | — |
| 9 | === !== == != |
相等运算 | — |
| 10 | & |
按位与 | — |
| 11 | ^ |
按位异或 | — |
| 12 | | |
按位或 | — |
| 13 | && |
逻辑与 | — |
| 14 | || |
逻辑或 | — |
| 15 | ?? |
空值合并 | ✅ ArkTS特有 |
| 16 | ?: |
三元条件 | — |
| 17 | = += -= *= /= %= **= &= |= ^= <<= >>= |
赋值运算符 | — |
⚠️ 关键优先级记忆点:
// ?? 优先级低于 ||,高于 ?:
// 必须加括号避免歧义
let a: number | null = null;
let b: number | null = 5;
// ✅ 加括号明确意图
let result: number = (a ?? 0) || (b ?? 0); // 正确
// ** 右结合性:从右到左计算
let power: number = 2 ** 3 ** 2; // 等于 2 ** (3 ** 2) = 2 ** 9 = 512
C++ vs ArkTS 运算符完整对照表
| 运算符类别 | C++ 运算符 | ArkTS 运算符 | ArkTS 不支持 | 说明 |
|---|---|---|---|---|
| 四则运算 | + - * / % |
+ - * / % |
— | / 结果总为浮点 ⚠️ |
| 幂运算 | pow() 函数 |
** |
— | ArkTS内置运算符 |
| 自增减 | ++ -- |
++ -- |
— | 完全相同 |
| 一元运算 | +x -x(支持隐式转换) |
+x -x(仅数值) |
字符串用一元+/- | ⚠️ 严格类型限制 |
| 位取反 | ~x |
~x |
— | 见位运算部分 |
| 字符串拼接 | +(需显式转换) |
+(string + 任意类型转字符串) |
— | 用模板字符串更清晰 |
| 严格相等 | ==(强类型) |
=== !== |
— | ✅ 始终用=== |
| 宽松相等 | — | == != |
— | ❌ 不推荐使用 |
| 关系比较 | > < >= <= |
> < >= <= |
— | 完全相同 |
| 逻辑运算 | && || ! |
&& || ! |
— | 完全相同 |
| 位运算 | & | ^ ~ << >> |
& | ^ ~ << >> >>> |
— | >>> 为ArkTS新增 |
| 赋值运算 | = += -= 等 |
= += -= **= 等 |
— | **= ArkTS增强 |
| 三元条件 | ?: |
?: |
— | 完全相同 |
| 空值合并 | 无 | ?? |
— | ✅ ArkTS特有 |
| 可选链 | 无(需手动判空) | ?. |
— | ✅ ArkTS特有 |
| 非空断言 | 无 | !(后缀) |
— | ✅ ArkTS特有 |
| 类型查询 | typeid(运行时) |
typeof(表达式中) |
typeof 用作类型 |
⚠️ 不能做类型注解 |
| 类型检查 | dynamic_cast |
instanceof(引用类型) |
基础类型左操作数 | ⚠️ 限引用类型 |
| 属性删除 | 无 | — | delete |
❌ ArkTS不支持 |
| 属性检查 | 无 | — | in |
❌ 用instanceof替代 |
| 逗号运算符 | (a, b) |
仅for循环 | 其他场景 | ⚠️ 受限支持 |

浙公网安备 33010602011771号