零基础鸿蒙应用开发第八节:流程控制之循环语句

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 理解循环语句的核心作用,掌握“重复执行相同/相似逻辑”的编程思维
  2. 掌握for循环的核心语法、计数器逻辑及典型场景(数组遍历、固定次数循环)
  3. 掌握for...of循环的简洁遍历方式,学会处理数组/字符串等可迭代对象(含索引获取技巧)
  4. 掌握while/do-while循环的语法差异,理解“先判断后执行”与“先执行后判断”的适用场景
  5. 掌握break/continue关键字的作用,学会优化循环流程(提前终止、过滤无效迭代)
  6. 能结合实际业务场景(数据批量处理、条件循环、迭代遍历)选择合适的循环语句,规避死循环

【学习重点】

  1. for循环的计数器需明确“起始值、终止条件、步长”,避免计数器越界或死循环
  2. for...of适用于可迭代对象遍历(数组/字符串),获取索引需配合Array.prototype.entries()
  3. while循环需确保“终止条件最终为false”,do-while至少执行一次循环体(即使条件为false)
  4. break终止整个循环,continue跳过当前迭代(仅终止单次循环),两者作用域为“最近的循环”
  5. 循环选型原则:固定次数循环用for,可迭代对象遍历用for...of,条件循环用while/do-while

一、工程结构

本节我们将创建名为FlowControlLoopDemo的工程,基于 鸿蒙5.0(API12) 开发,使用 DevEco Studio 6.0+ 工具,项目结构目录如下:

FlowControlLoopDemo
├── AppScope                 # 应用全局配置
├── entry
│   ├── src
│   │   ├── main
│   │   │   ├── ets          
│   │   │   │   ├── entryability   # 应用入口(无需修改)
│   │   │   │   ├── pages          
│   │   │   │   │   └── Index.ets  # 测试页面(调用循环函数)
│   │   │   │   └── utils          
│   │   │   │       └── FlowControlTest.ets # 循环核心代码
│   │   │   ├── resources        # 静态资源(无需修改)
│   │   │   └── module.json5     # 模块配置(无需修改)
│   │   └── ...(其他目录默认即可)
└── ...(其他目录默认即可)

前序章节内容我们已经对于工程的创建,新建文件夹以及代码文件足够熟练。后续内容不再重复讲如何创建工程以及文件。不熟悉的从开篇看。

二、循环语句核心概念:为什么需要循环?

循环语句解决“重复执行相同/相似逻辑”的问题,是处理批量数据、重复操作的核心工具。

生活场景类比:

  • 计算1-100的累加和(重复“加数字”操作100次);
  • 遍历购物车列表计算总价(重复“加单价”操作,次数=商品数量);
  • 验证用户输入直到合法(重复“输入校验”,直到条件满足);
  • 打印5遍“Hello ArkTS”(固定次数重复输出)。

编程中,循环语句(for/for...of/while/do-while)就是实现这类“重复操作”的核心语法。

三、for循环:固定次数的精准循环

for循环适用于已知循环次数的场景,核心是“计数器三要素”(初始化、终止条件、步长更新),是最常用的循环类型。

3.1 基础语法

for (初始化计数器; 终止条件; 计数器更新) {
  // 循环体:终止条件为true时执行的代码
}
  • 初始化计数器:仅执行1次,定义循环起始值(如let i = 0);
  • 终止条件:每次循环前判断,返回布尔值,为false时终止循环;
  • 计数器更新:每次循环体执行完后执行,控制循环步长(如i++/i += 2);
  • 作用域:推荐用let定义计数器(块级作用域),避免var的全局污染(简单了解,后边作用域会重点讲)。

3.2 代码示例

示例1:基础for循环(固定次数输出)

/**
 * 测试基础for循环:固定次数输出(打印5遍Hello ArkTS)
 */
export function testForBasic(): void {
  console.log(`\n========== 基础for循环(固定次数输出) ==========`);
  // 三要素:i从0开始,i<5时循环,每次i+1
  for (let i = 0; i < 5; i++) {
    console.log(`第${i+1}遍:Hello ArkTS`); 
  }
}

运行效果

========== 基础for循环(固定次数输出) ==========
第1遍:Hello ArkTS
第2遍:Hello ArkTS
第3遍:Hello ArkTS
第4遍:Hello ArkTS
第5遍:Hello ArkTS

示例2:for循环(数组遍历+购物车总价计算)

/**
 * 测试for循环:数组遍历(计算购物车商品总价)
 */
export function testForArray(): void {
  console.log(`\n========== for循环(数组遍历-购物车总价) ==========`);
  // 模拟购物车商品单价
  const cartPrices: number[] = [99, 199, 299, 49];
  let totalPrice: number = 0;
   
  // 遍历数组:i从0到数组长度-1(避免索引越界,数组下标默认从0开始)
  for (let i = 0; i < cartPrices.length; i++) {
    totalPrice += cartPrices[i];
    console.log(`第${i+1}个商品单价:${cartPrices[i]},当前总价:${totalPrice}`);
  }
  console.log(`购物车总价:${totalPrice}`);
}

运行效果

========== for循环(数组遍历-购物车总价) ==========
第1个商品单价:99,当前总价:99
第2个商品单价:199,当前总价:298
第3个商品单价:299,当前总价:597
第4个商品单价:49,当前总价:646
购物车总价:646

3.3 新手易错点

  1. 索引越界:终止条件写i <= cartPrices.length(数组最大索引为length-1);ArkTS中数组越界不会直接闪退,但会返回undefined,导致后续逻辑错误(如计算NaN),同样需要严格避免(其他开发语言中越界可能导致程序闪退);
  2. 死循环:遗漏i++或终止条件永远为true(如i > -1),循环永不停止;
  3. 步长矛盾:如i=10; i>0; i++(步长与终止条件反向,直接死循环);
  4. 计数器作用域:javascript中用var i = 0定义,循环结束后i仍可访问,易引发后续错误;ArkTS并不支持var声明变量(作为了解),只能使用let/const

四、for...of循环:可迭代对象的遍历

for...of适用于可迭代对象(数组、字符串、Set、Map等),无需手动管理计数器,代码更简洁。

4.1 基础语法

for (const 元素变量 of 可迭代对象) {
  // 循环体:依次遍历每个元素
}
  • 元素变量:推荐用const(遍历中值不变),需修改则用let
  • 可迭代对象:普通对象({})不可直接遍历,需转成数组(如Object.values(obj));
  • 索引获取:需配合Array.prototype.entries(),ArkTS不支持数组解构的简写形式(for (const [index,fruit] of fruits.entries())),需通过数组下标访问索引-元素对。

4.2 代码示例

示例1:基础for...of(购物车计算)

/**
 * 测试for...of循环:基础数组遍历(购物车总价)
 */
export function testForOfBasic(): void {
  console.log(`\n========== for...of循环(基础数组遍历) ==========`);
  const cartPrices: number[] = [99, 199, 299, 49];
  let totalPrice: number = 0;
  let index: number = 0; // 手动维护索引(可选)

  for (const price of cartPrices) {
    totalPrice += price;
    index++;
    console.log(`第${index}个商品单价:${price},当前总价:${totalPrice}`);
  }
  console.log(`购物车总价:${totalPrice}`);
}

运行效果:与testForArray完全一致。

示例2:for...of(带索引+字符串遍历)

/**
 * 测试for...of循环:带索引遍历+字符串遍历
 */
export function testForOfIndex(): void {
  console.log(`\n========== for...of循环(带索引+字符串遍历) ==========`);
  
  // 1. 数组带索引遍历(ArkTS推荐写法)
  const fruits: string[] = ["苹果", "香蕉", "橙子"];
  // entries()返回迭代器,每个元素是[index, value]数组,通过下标访问
  for (const item of fruits.entries()) {
    console.log(`索引${item[0]}:${item[1]}`);
  }

  // 2. 字符串遍历(按字符拆分)
  const str: string = "ArkTS";
  console.log(`\n字符串"${str}"字符遍历:`);
  for (const char of str) {
    console.log(`字符:${char}`);
  }
}

运行效果

========== for...of循环(带索引+字符串遍历) ==========
索引0:苹果
索引1:香蕉
索引2:橙子

字符串"ArkTS"字符遍历:
字符:A
字符:r
字符:k
字符:T
字符:S

4.3 新手易错点

  1. 遍历普通对象:直接用for...of遍历{name: "张三"}(普通对象不可迭代);
  2. 修改const元素:用const price却尝试price = 100(需改为let);
  3. 空值遍历:遍历undefined/null(非可迭代对象,直接报错)。

五、while/do-while循环:条件驱动的循环

适用于未知循环次数的场景,核心是“条件表达式”而非固定次数。

5.1 while循环(先判断后执行)

语法

初始化循环变量;
while (终止条件) {
  // 循环体:条件为true时执行
  更新循环变量; // 必须更新,否则死循环
}

代码示例(用户输入验证)

/**
 * 测试while循环:用户输入验证(模拟直到输入合法数字)
 */
export function testWhileBasic(): void {
  console.log(`\n========== while循环(用户输入验证) ==========`);
  // 模拟用户输入(实际为UI输入框取值)
  let userInput: string | number = "abc"; 
  let validNum: number = 0;

  // 循环条件:输入不是数字 或 数字小于0
  while (isNaN(Number(userInput)) || Number(userInput) < 0) {
    // 输入不符合要求给出提示
    console.log(`当前输入:${userInput} → 无效,请输入非负数字!`);
    // 模拟用户修正输入(第3次输入合法值)
    if (userInput === "abc") {
      // 第二次修改为 "-10"
      userInput = "-10";
    } else if (userInput === "-10") {
      // 第三次修改为 "99"
      userInput = "99";
    }
  }

  validNum = Number(userInput);
  console.log(`输入合法:${validNum},验证通过!`);
}

运行效果

========== while循环(用户输入验证) ==========
当前输入:abc → 无效,请输入非负数字!
当前输入:-10 → 无效,请输入非负数字!
输入合法:99,验证通过!

5.2 do-while循环(先执行后判断)

语法

初始化循环变量;
do {
  // 循环体:至少执行1次
  更新循环变量;
} while (终止条件);

代码示例(登录重试)

/**
 * 测试do-while循环:登录重试(至少执行1次验证)
 */
export function testDoWhileBasic(): void {
  console.log(`\n========== do-while循环(登录重试) ==========`);
  let retryCount: number = 0;
  const maxRetry: number = 3;
  let isLoginSuccess: boolean = false;

  // 至少执行1次登录验证
  do {
    retryCount++;
    console.log(`第${retryCount}次登录验证...`);
    // 模拟第3次登录成功
    if (retryCount === 3) {
      isLoginSuccess = true;
    }
  } while (!isLoginSuccess && retryCount < maxRetry);

  if (isLoginSuccess) {
    console.log("登录成功!");
  } else {
    console.log("重试次数耗尽,登录失败!");
  }
}

运行效果

========== do-while循环(登录重试) ==========
第1次登录验证...
第2次登录验证...
第3次登录验证...
登录成功!

5.3 核心差异

特性 while循环 do-while循环
执行顺序 先判断,后执行 先执行,后判断
执行次数 可能0次(初始条件为false) 至少1次
适用场景 不确定是否需要执行 必须执行至少1次

5.4 新手易错点

  1. 死循环while循环中遗漏更新变量(如忘记修改userInput);
  2. 分号遗漏do-while末尾忘记写;(语法报错);
  3. 条件永远为true:如while (1)(数字1会被隐式转换为布尔值true,等价于while (true),会触发死循环)。

六、break/continue:循环流程控制

用于手动干预循环执行,break终止整个循环,continue跳过当前迭代。

6.1 核心作用

关键字 作用 适用场景
break 立即终止整个循环,跳出循环体 找到目标值后提前终止循环(如查找第一个符合条件的元素)
continue 跳过当前迭代,进入下一次循环 过滤无效数据(如跳过空值/负数),仅处理有效值

6.2 代码示例

/**
 * 测试break/continue:优化循环流程
 */
export function testBreakContinue(): void {
  console.log(`\n========== break/continue(循环流程控制) ==========`);
  const nums: number[] = [1, 3, 5, 7, 8, 9, 10];
  
  // 1. break:找到第一个偶数后终止循环
  console.log("--- break示例(找第一个偶数) ---");
  for (const num of nums) {
    if (num % 2 === 0) {
      console.log(`找到第一个偶数:${num},终止循环`);
      break;
    }
    console.log(`当前数字:${num}(奇数)`);
  }

  // 2. continue:仅打印偶数,跳过奇数
  console.log("\n--- continue示例(仅打印偶数) ---");
  for (const num of nums) {
    if (num % 2 !== 0) {
      continue; // 跳过奇数
    }
    console.log(`偶数:${num}`);
  }
}

运行效果

========== break/continue(循环流程控制) ==========
--- break示例(找第一个偶数) ---
当前数字:1(奇数)
当前数字:3(奇数)
当前数字:5(奇数)
当前数字:7(奇数)
找到第一个偶数:8,终止循环

--- continue示例(仅打印偶数) ---
偶数:8
偶数:10

七、循环语句选型对比

循环类型 核心适用场景 优点 缺点
for 固定次数、数组索引遍历 精准控制次数/步长 代码稍繁琐,需管理计数器
for...of 可迭代对象遍历(无索引需求) 代码简洁,无需管理计数器 无法直接获取索引,不支持普通对象
while 未知次数、条件循环 灵活,仅依赖条件表达式 易遗漏变量更新导致死循环
do-while 必须执行至少1次的条件循环 保证至少执行1次 即使条件为false也会执行1次

八、内容总结

1. 核心选型原则

  • 固定次数循环 → for(如数组索引遍历、固定次数输出);
  • 可迭代对象遍历 → for...of(如数组/字符串遍历,无索引需求);
  • 未知次数、可能不执行 → while(如输入验证);
  • 未知次数、必须执行1次 → do-while(如登录重试)。

2. 新手必避3个坑

  • 死循环:确保循环变量最终能让终止条件为false
  • 索引越界:for循环终止条件用i < 数组长度
  • 类型错误:for...of不遍历普通对象,while条件必须是布尔值。

3. 最佳实践

  • 计数器用let(块级作用域);
  • for...of遍历数组优先用const
  • while/do-while先初始化变量,再写循环;
  • 循环内及时用break终止(如找到目标值后),提升性能。

九、测试页面

// pages/Index.ets
import { allTest } from '../utils/FlowControlTest';

@Entry
@Component
struct Index {
  // 页面加载时执行所有测试函数
  aboutToAppear(): void {
    allTest();
  }

  build() {
    Column() {
      Text("流程控制(下)——循环语句")
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Text("运行日志请查看控制台")
        .fontSize(16)
        .margin(20)
        .fontColor(Color.Grey)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center);
  }
}

十、代码仓库

十一、下节预告

下一节我们将进入流程控制综合实战,告别纯业务场景,用3个有趣又实用的经典编程案例,把前6节的核心知识点(循环+分支+数组+运算符)融会贯通,全程聚焦“知识点组合运用”,而非单纯堆砌代码,核心包括:

实战1:猜数字小游戏(入门级:while/do-while + if-else)

  • 巩固知识:do-while循环(至少执行1次)、if-else分支(判断猜大/猜小/猜对)、随机数生成(Number内置对象)、break循环终止、计数器控制次数限制;
  • 玩法设计:程序随机生成1-100的整数,用户最多猜7次,每次输入后提示“猜大了”“猜小了”,猜对或次数用尽后终止游戏,输出结果。

实战2:打印99乘法表(进阶级:嵌套for循环 + 字符串处理)

  • 巩固知识:嵌套for循环(外层控行、内层控列)、条件分支(控制列数避免重复)、字符串拼接/模板字符串(格式化输出)、算术运算符(乘法运算);
  • 输出效果:按三角格式打印1-9乘法表,格式对齐,直观呈现嵌套循环的逻辑。

实战3:生成杨辉三角(提升级:二维数组 + 多层循环)

  • 巩固知识:二维数组的创建与遍历、多层for循环(行/列逻辑控制)、条件分支(边界值处理)、算术运算符(相邻元素求和)、格式化输出;
  • 输出效果:生成指定行数的杨辉三角(如6行),清晰展示数组与循环的组合运用,为后续复杂数据处理打基础。

通过下节实战,你将不再孤立记忆语法,而是学会“根据需求选工具”——比如用do-while处理“至少执行1次”的场景,用嵌套for循环处理“多行多列”的结构,用二维数组存储复杂数据,真正掌握流程控制的核心思维,为后续函数封装、鸿蒙组件开发打下坚实基础。

posted @ 2026-01-17 11:08  鸿蒙-散修  阅读(1)  评论(0)    收藏  举报