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 == undefinedtrue,但 null === undefinedfalse

这是 ===== 最实际的区别。在 ArkTS 中:

  • === 时,nullundefined 不相等
  • 要同时判断两者,需要显式判断: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 的专用运算符,只有左侧为 nullundefined 时才返回右侧值

这与 || 的关键区别:||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循环 其他场景 ⚠️ 受限支持
posted @ 2026-04-07 09:41  thammer  阅读(2)  评论(0)    收藏  举报