C语言中的循环与迭代探索
立即解锁
发布时间: 2025-08-21 00:53:44 阅读量: 1 订阅数: 3 


C语言编程入门与实践
### C语言中的循环与迭代探索
在编程中,很多操作需要重复执行多次才能得到最终结果。如果直接复制代码来实现重复,会非常繁琐。C语言提供了多种循环语句,如`for`、`while`和`do...while`,还有`goto`语句,不过`goto`语句常被诟病。下面我们将详细探讨这些循环和迭代的方法。
#### 1. 理解重复操作
在编程里,我们常常需要重复执行一系列语句。比如对一组值中的每个成员进行计算,或者使用这组值中的所有成员进行计算。也可能需要遍历整个集合,查找所需的值、统计值的数量、进行某种计算或对集合进行操作,例如排序。
实现重复操作有多种方法,最简单但限制最多的是暴力法,这种方法不依赖具体语言。更灵活的方法是使用迭代或循环,C语言提供了`while`、`for`和`do...while`三种相关的循环语句,每种都有控制或延续表达式以及循环体,其中最通用的是`while`循环。此外,还有古老的`goto`标签循环方法。与其他语言不同,C语言没有`repeat...until`语句,但可以用其他语句轻松构建类似功能。
每个循环语句都由两个基本部分组成:
- 循环延续表达式
- 循环体
当循环延续表达式的值为真时,执行循环体,然后再次计算延续表达式,若仍为真,则继续执行循环体,直到延续表达式为假,循环结束,程序继续执行循环体之后的代码。
循环语句的延续表达式一般有两种类型:
- **计数器控制循环**:迭代次数取决于某种计数,期望的迭代次数事先已知,计数器可以递增或递减。
- **条件或哨兵控制循环**:迭代次数取决于某个条件是否为真,实际迭代次数未知,哨兵是循环结束前必须达到的某个状态的值。
本章主要探讨计数器控制循环,后续章节会在从控制台获取输入和从文件读取输入时再讨论哨兵控制循环。C语言还提供了`break`和`continue`等额外的循环控制机制,在简单计数器或哨兵值无法满足特殊需求时,能提供更强大的循环控制。
为了更好地理解迭代和重复,我们来看一个17世纪数学家高斯小时候遇到的问题。当时高斯上小学,老师为了打发课间时间,让学生计算1到100的和。其他学生都在逐个相加,而高斯很快想出了一个简单而优雅的公式:`sum(n) = n * (n + 1) / 2`,这里的`n`是从1开始的自然数序列中的最大数。
下面是用C语言实现高斯公式的函数:
```c
int sumNviaGauss( int N ) {
int sum = 0;
sum = N * ( N+1 ) / 2;
return sum;
}
```
这个函数的输入参数是`N`,返回值是1到`N`的整数之和。需要注意的是,公式`N * ( N + 1 ) / 2`中使用括号是因为`*`和`/`的优先级高于`+`,括号能确保得到正确结果。
提供这个解决方案的意义在于,作为C程序员,我们可以使用各种C语言语句来构建复杂的计算,但要记住,可能已经存在更简单、更通用的方程或算法。因此,每个程序员都应该熟悉《Numerical Recipes in X》系列书籍,它们用C、Fortran或C++等语言提供了一些复杂数学问题的解决方案。
#### 2. 理解暴力重复
暴力重复就是将需要重复的语句或语句系列复制所需的次数。这种方法的重复次数是硬编码的,运行时无法更改,是最具限制性的重复形式。
暴力重复有几个缺点:
- **修改困难**:如果需要修改其中一个或多个被复制的语句,修改所有语句会很繁琐且容易出错,删除、更正和重新复制行也同样如此。
- **代码臃肿**:复制少量行还可以接受,但复制100行或1000行代码会使代码变得不必要地庞大。
不过,在某些高级主题(如循环展开)中,复制单个语句多次是必要的,但本书不涉及这个内容。
下面是用暴力法计算1到100之和的函数:
```c
int sum100bruteForce( void ) {
int sum = 0;
sum = 1;
sum += 2;
sum += 3;
// ...
sum += 99;
sum += 100;
return sum;
}
```
这个函数虽然能正确计算1到100的和,但只能处理这一个序列,要计算1到10或1到50的和,需要使用不同的暴力方法。
还有一个更通用一点的版本:
```c
int sum100bruteForce2( void ) {
int sum = 0;
int num = 1;
sum = num;
sum += ++num;
sum += ++num;
sum += ++num;
// ...
sum += ++num;
sum += ++num; // 100
return sum;
}
```
这个版本虽然避免了直接输入1到100的每个值,但同样繁琐,而且更难编写,因为很难跟踪`sum += ++num;`语句的复制次数。
下面是`gauss_bruteforce.c`程序的`main`函数:
```c
#include <stdio.h>
#include <stdbool.h>
int sum100bruteForce( void );
int sum100bruteForce2( void );
int sumNviaGauss( int N );
int main( void ) {
int n = 100;
printf( "The sum of 1..100 = %d (via brute force)\n" ,
sum100bruteForce() );
printf( "The sum of 1..100 = %d (via brute force2)\n" ,
sum100bruteForce2() );
printf( "The sum of 1..%d = %d (via Gaussian insight)\n" ,
n , sumNviaGauss( n ) );
return 0;
}
```
创建`gauss_bruteforce.c`文件,输入`main`函数和三个求和函数,使用`cc gauss_bruteforce.c -o gauss_bruteforce`命令编译程序,运行后会发现三种方法计算1到100的和结果相同。
在研究各种重复方法时,我们每次都用高斯的问题来举例,这样做有两个好处:一是在使用循环迭代计数器时,计数器条件中的起始或停止错误会导致和不同,通过已知结果的问题可以验证代码;二是因为已经用两种方法解决了这个问题,所以对问题比较熟悉,可以更专注于每种循环方法的语法差异。
#### 3. 介绍`while`语句
`while`语句的语法如下:
```plaintext
while( continuation_expression ) statement_body
```
计算`continuation_expression`的值,如果为真,则执行`statement_body`,然后重复这个过程。当`continuation_expression`为假时,循环结束,程序继续执行`statement_body`之后的代码。如果`continuation_expression`一开始就为假,则`statement_body`不会执行。
`statement_body`可以是单个语句,甚至是空语句(单个分号),但通常是复合语句。注意,`while`语句本身没有分号,分号可能出现在`statement_body`中的单个语句里,或者在`statement_body`是复合语句`{ ... }`时不出现。
在`statement_body`中,必须有某种方法改变`continuation_expression`中使用的值,否则循环要么永远不执行,要么一旦开始就永远不会结束(即无限循环)。在计数器控制循环中,计数器必须在循环体的某个地方更新。
下面是用`while`循环解决高斯问题的函数:
```c
int sumNviaWhile( int N ) {
int sum = 0;
int num = 0;
while( num < N ) // num: 0..99 (100 is not less than 100) {
sum += (num+1); // Off-by-one: shift 0..99 to 1..100.
num++;
}
return sum;
}
```
这里会遇到“差一错误”问题,在很多编程语言中都存在。我们的计数器从0开始到`N - 1`,进行`N`次迭代。也可以从1开始,检查计数器是否小于`N + 1`,或者从0开始,检查计数器是否小于等于`N`,但后一种方法会进行`N + 1`次迭代。我们选择从0开始计数,是因为C语言数组索引从0开始,提前习惯零基计数和索引,在后续处理数组索引和指针加法时会减少很多麻烦。为了避免计数器范围带来的混淆,在注释中注明计数器或索引的预期值范围很有帮助。
还有另一种实现`while`循环的方法,即向下计数。我们可以使用输入参数`N`作为计数器,而不需要单独的计数变量。函数参数会从调用者复制过来,成为函数内的局部变量。我们将`N`作为局部变量作为计数器,递减它,让0作为停止条件,因为0在C语言中表示假。
```c
int sumNviaWhile2( int N ) {
int sum = 0;
while( N ) { // N: N down to 1 (stops at 0).
sum += N;
N--;
}
return sum;
}
```
这两种方法没有绝对的优劣之分,在这个简单问题中体现不明显,但当`statement_body`变得复杂时,一种方法可能在清晰度和可读性上更有优势。这里的关键是展示从不同角度思考问题有时能让代码更清晰。
#### 4. 介绍`for`语句
`for`语句的语法如下:
```plaintext
for( counter_initialization ; continuation_expression ; counter_increment ) statement_body
```
`for`语句由三部分控制表达式和一个语句体组成。控制表达式包括计数器初始化表达式、延续表达式和计数器增量表达式,各部分用分号分隔,每个部分都有明确的用途,位置不能互换。
执行`for`语句时,首先计算计数器初始化表达式,且只计算一次。然后计算延续表达式,若为真,则执行语句体,语句体执行结束后,计算计数器增量表达式,接着再次计算延续表达式,重复这个过程。当延续表达式为假时,循环结束,程序继续执行语句体之后的代码。如果延续表达式一开始就为假,则语句体不会执行。
`statement_body`可以是单个语句,甚至是空语句,但通常是复合语句。注意,`for`语句本身没有分号,分号可能出现在`statement_body`中的单个语句里,或者在`statement_body`是复合语句`{ ... }`时不出现。
在`for`语句中,所有控制元素都在循环开始处,这样设计是为了将所有控制元素放在一起,当语句体复杂或过长时,这种结构非常有用,因为不会丢失控制元素。
计数器增量表达式可以是任何能使计数器递增、递减或改变的表达式。当计数器在`for`循环内声明并初始化时,它只能在该`for`循环的语句体内使用,类似于函数参数是函数体的局部变量。
下面是用`for`循环解决高斯问题的函数:
```c
int sumNviaFor( int N ) {
int sum = 0;
for( int num = 0 ; num < N ; num++ ) { // num: 0..99 (it's a C thing)
sum += (num+1); // Off-by-one: shift 0..99 to 1..100.
}
return sum;
}
```
和`while`循环一样,这里也会遇到“差一错误”问题。还有另一种实现`for`循环的方法,即向下计数,使用输入参数`N`作为计数器,递减它,让0作为停止条件。
```c
int sumNviaFor2( int N ) {
int sum = 0;
for( int i = N ; // range: 100..1
i > 0 ; // stops at 1.
i-- ) {
sum += i; // No off-by-one.
}
return sum;
}
```
在`sumNviaFor2`函数中,控制表达式的各部分格式化为每行一个部分,这样可以处理更复杂的表达式,并添加注释。例如,假设我们想同时使用两个计数器,一个向上计数,一个向下计数,可以在计数器初始化表达式和计数器增量表达式中使用逗号运算符来初始化和递增多个计数器。
```c
for( int i = 0 , int j = maxLen ;
(i < m
```
0
0
复制全文
相关推荐










