当前位置: 首页 > news >正文

C程序中的循环语句

C程序中的循环语句

循环是重复执行其他语句的一种语句结构,它让程序能够自动化重复性任务。在C语言中,每个循环都有一个控制表达式,这个表达式就像一个守门员,决定着循环体是否继续执行。每次执行循环体时都要对控制表达式求值,如果表达式为真(即值不为零),循环继续;如果为假(值为零),循环终止。这种机制使得程序能够根据条件灵活地控制重复执行的次数。

while语句:循环的基石

while语句是C语言中最简单也是最基本的循环形式,它的语法结构清晰明了:while (表达式) 语句。这里的圆括号是强制要求的,而且在右括号和循环体之间没有任何额外的关键字(不像某些语言需要do关键字)。执行while语句时,程序首先计算控制表达式的值,如果值不为零(真),就执行循环体,接着再次判定表达式,这个"判定-执行"的过程会持续进行直到控制表达式的值变为零。

让我们分析一个计算2的幂的例子,这个程序要找出大于或等于给定数n的最小的2的幂:

i = 1;
while (i < n)i = i * 2;

假设n的值为10,让我们逐步追踪这个循环的执行过程。初始时i为1,小于10,所以进入循环体,i变为2;第二次判定时2仍小于10,i变为4;第三次判定时4小于10,i变为8;第四次判定时8小于10,i变为16;第五次判定时16不小于10,循环终止。最终i的值是16,这正是大于或等于10的最小的2的幂。这个简单的例子展示了while循环的核心特性:控制表达式在循环体执行之前进行判定。

当循环体需要包含多条语句时,我们使用花括号将它们组合成复合语句。考虑这个倒计数程序:

i = 10;
while (i > 0) {printf("T minus %d and counting\n", i);i--;
}

这段代码从10开始倒计数,每次循环都输出一条消息并将i递减。程序首先检查i是否大于0(是的,10大于0),然后打印"T minus 10 and counting"并将i减为9。这个过程继续进行,依次打印9、8、7…直到打印"T minus 1 and counting"后i变为0。此时条件i > 0不再成立,循环终止。

值得注意的是,while循环有一个重要特性:循环体可能一次都不执行。如果控制表达式在第一次判定时就为假,循环体会被完全跳过。比如在上面的倒计数程序中,如果i的初始值是0或负数,循环体将不会执行,程序不会打印任何消息。

while语句经常可以有多种写法。倒计数循环可以写得更加紧凑:

while (i > 0)printf("T minus %d and counting\n", i--);

这个版本将递减操作移到了printf函数调用内部,利用后缀递减运算符的特性:先使用i的值,然后再递减。这种写法虽然更简洁,但可能会牺牲一些可读性。

do语句:保证至少执行一次

do语句是while语句的变体,它们的关键区别在于控制表达式的判定时机。do语句的格式是:do 语句 while (表达式);。注意末尾的分号是必需的。执行do语句时,先执行循环体,然后计算控制表达式的值。如果表达式的值非零,再次执行循环体,然后再次计算表达式。这种"执行-判定"的顺序保证了循环体至少执行一次。

用do语句重写倒计数程序:

i = 10;
do {printf("T minus %d and counting\n", i);--i;
} while (i > 0);

程序首先执行循环体,打印"T minus 10 and counting"并将i减为9。然后判定条件i > 0,因为9大于0,所以继续执行循环。这个过程持续到打印"T minus 1 and counting"并将i减为0后,条件判定失败,循环终止。表面上看,这个程序与while版本的行为相同,但如果i的初始值是0或负数,do版本仍会打印一条消息,而while版本则不会。

do语句在需要至少执行一次循环体的场景中特别有用,一个经典的例子是计算整数的位数:

/* numdigit.c - 计算非负整数的位数 */
#include <stdio.h>int main(void)
{int digits = 0, n;printf("Enter a nonnegative integer: ");scanf("%d", &n);do {n /= 10;        // 去掉最低位digits++;       // 位数计数器加1} while (n > 0);    // 如果还有数字,继续printf("The number has %d digit(s).\n", digits);return 0;
}

这个程序通过反复除以10来计算位数。关键在于,即使输入是0,它也有一位数字。如果使用while循环,当输入0时,循环体不会执行,程序会错误地报告"0位数字"。而do循环确保至少执行一次,正确地处理了这个边界情况。程序的工作原理是:每次除以10相当于去掉一位十进制数字,统计除法的次数就得到了位数。顺便提一下,无论需要与否,最好给所有的do语句都加上花括号。没有花括号的do语句很容易被误认为是while语句,造成阅读上的困惑。

for语句:最强大的循环结构

for语句是C语言中功能最强大、最灵活的循环结构,它的格式是:for (表达式1; 表达式2; 表达式3) 语句。这三个表达式各有其职:表达式1是初始化步骤(只执行一次),表达式2控制循环的终止(每次循环前判定),表达式3是更新操作(每次循环后执行)。

让我们用for语句重写倒计数程序:

for (i = 10; i > 0; i--)printf("T minus %d and counting\n", i);

执行这个for语句时,首先i被初始化为10,然后判定i > 0是否为真。因为10大于0,所以打印第一条消息,然后执行i--将i减为9。接着再次判定i > 0,循环继续。这个过程重复10次,i的值从10递减到1。

for语句和while语句关系紧密,大多数for循环都可以转换为等价的while循环:

表达式1;
while (表达式2) {语句表达式3;
}

应用这个模式,我们的倒计数for循环等价于:

i = 10;
while (i > 0) {printf("T minus %d and counting\n", i);i--;
}

这种转换帮助我们理解for语句的执行流程。表达式1是循环开始前的初始化,只执行一次;表达式2控制循环终止,只要它不为零,循环就继续;表达式3是每次循环的最后操作。由于第一个和第三个表达式都是以语句的方式执行的,它们的值并不重要,重要的是它们的副作用(如赋值或自增)。

for语句的惯用法

经过长期实践,C程序员总结出了一些for循环的标准写法。对于"向上加"或"向下减"的计数循环,常见的模式包括:

从0向上加到n-1(数组索引的典型用法):

for (i = 0; i < n; i++)/* 处理array[i] */

从1向上加到n(自然计数):

for (i = 1; i <= n; i++)/* 处理第i个元素 */

从n-1向下减到0(反向遍历数组):

for (i = n - 1; i >= 0; i--)/* 反向处理array[i] */

从n向下减到1(反向自然计数):

for (i = n; i > 0; i--)/* 反向处理第i个元素 */

遵循这些惯用法能帮助避免常见错误,如"差一错误"(off-by-one error)——这是循环边界条件设置不当导致的,可能多执行或少执行一次循环。

灵活运用for语句

for语句的强大之处在于它的灵活性。三个表达式可以任意省略,甚至可以全部省略。省略第一个表达式意味着没有初始化:

i = 10;
for (; i > 0; --i)printf("T minus %d and counting\n", i);

省略第三个表达式时,需要在循环体中更新控制变量:

for (i = 10; i > 0;)printf("T minus %d and counting\n", i--);

同时省略第一个和第三个表达式,for语句就变得像while语句:

for (; i > 0;)printf("T minus %d and counting\n", i--);

省略第二个表达式会创建无限循环(缺失的条件默认为真):

for (;;)/* 无限循环 */

这是创建无限循环的惯用写法,比while(1)更受传统C程序员青睐。

C99引入了一个重要特性:可以在for语句中直接声明循环变量:

for (int i = 0; i < n; i++) {printf("%d ", i);  // i在循环内可见
}
// printf("%d", i);  // 错误!i在循环外不可见

这种写法将变量的作用域限制在循环内部,提高了代码的封装性和可读性。如果需要在循环外使用循环变量的最终值,则必须在循环外声明变量。

逗号运算符:在for语句中的妙用

逗号运算符允许将两个表达式"粘贴"成一个表达式。格式是表达式1, 表达式2。执行时先计算表达式1(其值被丢弃),然后计算表达式2(作为整个逗号表达式的值)。逗号运算符是左结合的,优先级低于所有其他运算符。

在for循环中,逗号运算符常用于同时操作多个变量:

for (sum = 0, i = 1; i <= N; i++)sum += i;

这里在初始化部分同时设置了sum和i的值。逗号表达式sum = 0, i = 1先将0赋给sum,然后将1赋给i。

让我们看一个更复杂的例子,展示for语句的极大灵活性:

/* square3.c - 使用奇数法打印平方表 */
#include <stdio.h>int main(void)
{int i, n, odd, square;printf("This program prints a table of squares.\n");printf("Enter number of entries in table: ");scanf("%d", &n);i = 1;      // 要计算平方的数odd = 3;    // 下一个要加的奇数for (square = 1; i <= n; odd += 2) {printf("%10d%10d\n", i, square);++i;square += odd;}return 0;
}

这个程序利用了数学性质:连续整数的平方之间的差是连续奇数(1, 3, 5, 7…)。for语句初始化square为1(1的平方),测试i是否小于等于n,每次循环后将odd增加2。循环体中,i递增,square加上当前的奇数得到下一个平方数。这种方法避免了乘法运算,在早期计算机上可能更高效。

循环控制语句:break、continue和goto

break语句:提前退出循环

break语句提供了从循环中间退出的能力。它将控制从包含它的最内层while、do、for或switch语句中转移出去。在寻找素数的例子中:

for (d = 2; d < n; d++)if (n % d == 0)break;if (d < n)printf("%d is divisible by %d\n", n, d);
elseprintf("%d is prime\n", n);

这个循环尝试用2到n-1之间的所有数去除n。一旦发现n能被某个数d整除(n % d == 0),就没必要继续测试了,break语句立即退出循环。循环结束后,通过检查d的值可以判断循环是正常结束(d等于n,说明n是素数)还是提前退出(d小于n,说明找到了因子)。

break语句在读取用户输入直到特定值的场景中特别有用:

for (;;) {printf("Enter a number (enter 0 to stop): ");scanf("%d", &n);if (n == 0)break;printf("%d cubed is %d\n", n, n * n * n);
}

这是一个无限循环,只有当用户输入0时才通过break退出。注意break语句只能跳出一层嵌套,在嵌套结构中要特别注意。

continue语句:跳过本次迭代

continue语句不会退出循环,而是跳过当前迭代的剩余部分,直接进入下一次迭代。它将控制转移到循环体末尾之前的位置。看这个读取非零数并求和的例子:

n = 0;
sum = 0;
while (n < 10) {scanf("%d", &i);if (i == 0)continue;  // 跳过0,继续读下一个数sum += i;n++;/* continue跳转到这里 */
}

程序读取整数,如果是0就跳过不计入总和,也不增加计数。只有非零数才会被加到sum中并使计数器n递增。这个循环会继续直到读取了10个非零数为止。

goto语句:无条件跳转

goto语句可以跳转到函数内任何有标号的语句处。标号是放在语句前的标识符,后面跟冒号。虽然goto在现代编程中很少使用,但在某些情况下它仍然有价值,特别是从深层嵌套中退出:

while (...) {switch (...) {...goto loop_done;  /* break只能跳出switch,不能跳出while */...}
}
loop_done: ...

在这种情况下,break语句只能跳出switch,无法跳出外层的while循环。goto提供了一种直接跳出多层嵌套的方法。然而,过度使用goto会导致"意大利面条代码"(spaghetti code),使程序难以理解和维护。

空语句的艺术

空语句就是只有分号的语句。它在创建空循环体时特别有用。考虑素数判定的优化版本:

for (d = 2; d < n && n % d != 0; d++)/* 空循环体 */;

这里把除法测试移到了控制表达式中。循环继续的条件是d < nn % d != 0(n不能被d整除)。一旦找到因子或d达到n,循环就终止。空语句通常单独放在一行,并加上注释,以明确这是有意为之。

程序员需要特别小心,避免无意中创建空语句。一个常见的错误是在控制结构后误加分号:

if (d == 0);              /* 错误!创建了空语句 */printf("Error: Division by zero\n");  /* 总是执行 */while (i > 0);            /* 错误!创建了无限循环 */
{printf("T minus %d and counting\n", i);--i;
}for (i = 10; i > 0; i--); /* 错误!循环体变成空语句 */printf("T minus %d and counting\n", i);  /* 只执行一次,打印0 */

这些错误会导致程序行为完全不符合预期,调试时可能很难发现。

实践案例:完整的菜单驱动程序

让我们通过一个完整的账簿平衡程序来综合运用所学的循环知识:

/* checking.c - 账簿平衡程序 */
#include <stdio.h>int main(void)
{int cmd;float balance = 0.0f, credit, debit;printf("*** ACME checkbook-balancing program ***\n");printf("Commands: 0=clear, 1=credit, 2=debit, ");printf("3=balance, 4=exit\n\n");for (;;) {  /* 无限循环,直到用户选择退出 */printf("Enter command: ");scanf("%d", &cmd);switch (cmd) {case 0:  /* 清空账户 */balance = 0.0f;break;case 1:  /* 存款 */printf("Enter amount of credit: ");scanf("%f", &credit);balance += credit;break;case 2:  /* 取款 */printf("Enter amount of debit: ");scanf("%f", &debit);balance -= debit;break;case 3:  /* 显示余额 */printf("Current balance: $%.2f\n", balance);break;case 4:  /* 退出程序 */return 0;  /* 直接返回,结束程序 */default:  /* 无效命令 */printf("Commands: 0=clear, 1=credit, 2=debit, ");printf("3=balance, 4=exit\n\n");break;}}
}

这个程序展示了几个重要概念的综合应用。首先,使用for(;;)创建了程序的主循环,这是一个无限循环,只有通过return语句才能退出。其次,switch语句处理用户的菜单选择,每个case对应一个功能。注意case 4中使用return 0直接结束整个程序,这比使用break加标志变量更简洁。程序维护一个浮点数balance来跟踪账户余额,通过简单的加减操作模拟存取款。

另一个值得研究的程序是改进版的平方表生成器:

/* square2.c - 使用for语句打印平方表 */
#include <stdio.h>int main(void)
{int i, n;printf("This program prints a table of squares.\n");printf("Enter number of entries in table: ");scanf("%d", &n);for (i = 1; i <= n; i++)printf("%10d%10d\n", i, i * i);return 0;
}

这个程序使用%10d格式说明符使输出对齐成整齐的列。数字在10个字符宽度内右对齐,创建了美观的表格输出。

附录:代码解析

A. 循环优化技巧

将普通循环转换为空循环体形式有时能提高代码的简洁性。考虑这个数列求和程序的演变:

/* sum.c - 数列求和(原始版本) */
#include <stdio.h>int main(void)
{int n, sum = 0;printf("This program sums a series of integers.\n");printf("Enter integers (0 to terminate): ");scanf("%d", &n);while (n != 0) {sum += n;scanf("%d", &n);}printf("The sum is: %d\n", sum);return 0;
}

这个程序有两个相同的scanf调用,一个在循环前,一个在循环内。这种重复在使用while循环时很常见。程序的逻辑是:先读一个数,如果不是0就加到总和中,然后读下一个数。条件n != 0在数被读入后立即判定,确保0不会被加到总和中。

B. 循环变体分析

前面展示的利用奇数计算平方的程序体现了一个数学原理:n² = 1 + 3 + 5 + … + (2n-1)。让我们追踪程序执行来验证这一点:

  • i=1: square=1(正确,1²=1)
  • i=2: square=1+3=4(正确,2²=4)
  • i=3: square=4+5=9(正确,3²=9)
  • i=4: square=9+7=16(正确,4²=16)

这个算法在没有硬件乘法器的早期计算机上特别有价值。现代编译器的优化使得这种技巧的性能优势不再明显,但它仍然是理解for语句灵活性的绝佳例子。

C. 嵌套循环的goto应用

虽然goto通常应该避免,但在某些情况下它是最清晰的解决方案。考虑在二维数组中搜索特定值:

for (i = 0; i < m; i++)for (j = 0; j < n; j++)if (a[i][j] == target)goto found;
/* 没找到目标 */
printf("Target not found\n");
goto done;found:printf("Found at position (%d, %d)\n", i, j);
done:/* 继续程序 */

使用break只能跳出内层循环,需要额外的标志变量才能跳出外层循环。goto在这里提供了直接而清晰的解决方案。

D. while与for的微妙差异

虽然大多数for循环可以转换为while循环,但含有continue语句时情况会变复杂:

/* for循环版本 */
sum = 0;
for (n = 0; n < 10; n++) {scanf("%d", &i);if (i == 0)continue;sum += i;
}/* 错误的while转换 */
sum = 0;
n = 0;
while (n < 10) {scanf("%d", &i);if (i == 0)continue;sum += i;n++;  /* continue会跳过这里! */
}

在for循环中,continue跳转到更新表达式(n++)之前,所以n总会递增。但在while循环中,continue跳过了n++,导致循环可能永不终止。正确的while版本需要不同的结构。

E. 循环不变式的概念

理解循环的一个强大工具是循环不变式——在每次迭代开始时都为真的条件。例如,在计算2的幂的循环中:

i = 1;
while (i < n)i = i * 2;
/* 循环不变式:i是2的幂 */

循环不变式是:i始终是2的某个幂。初始时i=1=2⁰,每次迭代将i乘以2保持这个性质。循环终止时,不变式仍然成立,且i >= n,因此i是大于等于n的最小2的幂。

F. 防御性编程实践

在实际编程中,应该考虑边界情况和错误处理。例如,改进的数字位数计算程序:

#include <stdio.h>
#include <limits.h>int main(void)
{int digits = 0, n;printf("Enter a nonnegative integer: ");if (scanf("%d", &n) != 1) {printf("Invalid input\n");return 1;}if (n < 0) {printf("Number must be nonnegative\n");return 1;}/* 处理特殊情况 */if (n == 0) {printf("The number has 1 digit(s).\n");return 0;}/* 一般情况 */while (n > 0) {n /= 10;digits++;/* 防止溢出 */if (digits > 10) {  /* int最多10位 */printf("Number too large\n");return 1;}}printf("The number has %d digit(s).\n", digits);return 0;
}

这个版本增加了输入验证、负数检查和溢出保护,使程序更加健壮。

G. 性能考虑

虽然现代编译器优化很强大,但了解循环的性能特性仍然重要。例如,向下计数循环通常比向上计数略快:

/* 向上计数 */
for (i = 0; i < n; i++)process(i);/* 向下计数(可能更快) */
for (i = n - 1; i >= 0; i--)process(i);

向下计数的优势在于比较操作(与0比较)可能比与变量比较更快。但这种差异在现代处理器上通常可以忽略不计,代码的清晰性应该优先于微小的性能差异。

http://www.dtcms.com/a/499841.html

相关文章:

  • 湖南省城乡建设厅网站邮箱网站怎么做
  • Linux基础指令(完结)、shell命令与Linux权限(1) |tar|bc|uname|热键|shutdown|shell|权限
  • 能看网站的浏览器wordpress 自定义注册表单
  • 佛山网页网站设计做网站要多少人
  • DeepSeek辅助利用搬移底层xml实现快速编辑xlsx文件的python程序
  • 营销型外贸网站建设医疗网站怎么做seo
  • 【Swift】LeetCode 3. 无重复的最长子串
  • 深圳品牌网站建设公司招聘百度账号中心官网
  • jdk.nio.zipfs 包详解
  • 小说网站建设目的车牌照损坏在网站做的能用吗
  • 专业提供网站建设服务包括wordpress 联系人表单
  • Spring 核心原理:Bean 作用域、生命周期与 SpringBoot 自动配置
  • [MLflow] 环境管理 | MLflow模型 | Flavors与pyfunc
  • iis网站防盗链浙江鼎兴建设有限公司网站
  • python+django/flask的在线心理咨询系统
  • 继电保护:距离保护:过渡电阻影响
  • FAST DDS-GEN--通过 IDL 定义数据类型
  • 网站建设推广信息企业网站 建设流程
  • 连云港建设局网站网站建设培训南宁
  • uni-ap 地图报错Map key not configured
  • 新版 perf 文件解读与性能分析
  • JAVA国际版图文短视频交友系统源码支持H5 + APP
  • 【图像处理】图片的前向映射与后向映射
  • K8S(十三)—— Helm3从入门到实战:简化Kubernetes应用部署与管理
  • 如何录制视频,用 OBS
  • 网站地址做图标大型公司网站建设
  • 图片下载网站哪个好自己建设博客网站
  • 宝安哪有网站建设网站制作公司优势
  • 数据结构之——线性表的应用
  • Streamlit 交互与人python工智能网站开发基础