C primer plus(学习笔记)—— 第六章 C控制语句:循环

目录

6.1 再探while循环

6.1.1 程序注释

终止原理

EOF

scanf()的双重特性

1. 输入函数:

2. 格式化函数:

双重特性的体现:

使用注意事项:

6.1.2 C风格读取循环

6.2 while语句

6.2.1 终止while循环

6.2.2 何时终止循环

6.2.3 while:入口条件循环

6.2.4 语法要点

6.3 用关系运算符和表达式比较大小 

6.3.1 什么是真

6.3.2 其他真值

6.3.3 真值的问题

6.3.4 新的_Bool类型

6.3.5 优先级和关系运算符        

6.4 不确定循环和计数循环

6.5 for循环

利用for的灵活性

6.6 其他赋值运算符(运算符优先级表里有)

6.7 逗号运算符

6.8 出口条件循环 do while (先做一次,再检查条件)

6.9 如何选择循环

6.10 嵌套循环

6.11 数组简介

6.12 使用函数返回值的循环示例

6.12.1 math.h库——pow()幂函数

6.12.2 使用带返回值的函数

1. 返回值:

2. 无返回值:

3. 从条件语句返回:

4. 提前退出函数:

5. 返回字符串:

6. 返回多个值:

7. 返回错误码:


6.1 再探while循环

#include <stdio.h>

int main(void)
{
    long num;
    long sum = 0L;
    int status;

    printf("Please enter an integer to be summed");
    printf("(q to Quit)");
    status = scanf("%ld",&num);
    while (status == 1)
    {
        sum = sum +num;
        printf("Please enter next integer (q to Quit):");
        status = scanf("%ld",&num);
    }
    printf("Those integers sum to %ld.\n",sum);
    return 0;
}

6.1.1 程序注释

再次提醒 == 运算符是C的相等运算符

终止原理

在这里利用scanf()的一个特点;即如果用户输入的不是数字(如:q)scanf()会读取失败并返回0;此时,status的值就是0;循环结束。因为输入的字符q不是数字,所以它会被放回输入队列中(实际上,不仅仅是q,任何非数值的数据都会导致循环终止

EOF

如果 scanf 在读取过程中遇到问题,比如文件结束(EOF,End Of File)或者输入/输出错误,它会返回一个特殊的值 EOF

scanf 返回 EOF 时,它通常意味着读取操作没有成功完成。在循环中使用 scanf 时,如果 scanf 返回 EOF,循环会终止。这是因为循环通常依赖于 scanf 的返回值来确定是否继续读取更多的输入。

scanf()的双重特性

`scanf()` 函数在 C 语言中具有双重特性,它既是一个输入函数,也是一个格式化函数。这两个特性使得 `scanf()` 非常强大,但同时也需要程序员在使用时格外小心。

1. 输入函数:


   `scanf()` 的主要目的是从标准输入(通常是键盘)读取用户输入的数据。它可以根据指定的格式字符串解析输入流,并根据这些格式将输入的字符串转换为相应的数据类型,然后存储在提供的变量地址中。

2. 格式化函数:


   `scanf()` 通过格式字符串来控制输入数据的解析方式。格式字符串定义了期望的输入格式,包括数据类型、字段宽度、分隔符等。这使得 `scanf()` 能够灵活地处理各种不同的输入情况。

双重特性的体现:
  • 数据类型转换:

  `scanf()` 根据格式字符串中的格式说明符(如 `%d`、`%f`、`%s` 等)自动将输入的字符串转换为相应的数据类型。例如,`%d` 将输入的数字字符串转换为整数,`%f` 将输入的数字字符串转换为浮点数。

  • 输入控制:

  格式字符串中的限定符(如宽度限定符 `3` 或 `*`)可以用来控制输入的读取,例如,`%3s` 会读取最多三个字符的字符串,`%*d` 会跳过下一个整数输入。

  • 错误处理:

  `scanf()` 在遇到输入错误时(如类型不匹配、超出预期的输入等)会有不同的行为。它可能跳过错误的输入,或者返回一个错误标志(通常是 `EOF`),这需要程序员进行适当的错误检查和处理。

使用注意事项:
  • 缓冲区溢出:

  如果输入的数据超过了为变量分配的空间,可能会导致缓冲区溢出,这是常见的安全漏洞。

  • 输入验证:

  由于 `scanf()` 会根据格式字符串解析输入,如果输入不符合预期格式,可能会导致未定义的行为。因此,在使用 `scanf()` 时,应该总是检查其返回值,并进行适当的错误处理。

  • 安全性:

  在处理来自不可信源的输入时,应该谨慎使用 `scanf()`,因为它可能被用来执行格式化字符串攻击。

6.1.2 C风格读取循环

while (scanf("%ld", &num) == 1)

{

/*循环行为*/

}

6.2 while语句

while 循环的通用形式:
  while (expression)
    statement
while 循环是 入口条件循环,满足条件时才进入循环体。
只有while(expression) 后的单独语句(或者{}扩起的语句块)才是 循环部分

每一次循环都被称为一次迭代

6.2.1 终止while循环

构建while循环时,测试表达式(即while后面的expression)的值 最终要为假。否则,就会出现“死循环”。

可以使用break和if语句来终止循环

6.2.2 何时终止循环

6.2.3 while:入口条件循环

while循环是使用入口条件(entry condition)的有条件循环。入口条件是指,只要满足条件时才进入循环体,不满足时就直接跳过循环。

6.2.4 语法要点

只有测试条件后面的单独语句(大括号扩起的复合语句也是单独语句。空语句; 也是单独语句)才是循环部分。

#include <stdio.h>

int main(void)
{
    int n = 0;
    while (n < 3)
        printf("n is %d\n", n);
        n++;   //注意这句不是循环体的内容,n++不会执行,n<3一直成立,所以上面的循环会一直进行。
        printf("That's all this program does\n");
        return 0;
}

有时程序员会故意使用带空语句的while语句原因可能是:

1. 等待硬件操作:
   在嵌入式系统或与硬件直接交互的程序中,程序员可能需要等待硬件完成某些操作。在这种情况下,使用空循环可以提供一个简单的等待机制,直到硬件准备好进行下一步操作。

2. 简化代码:
   在某些情况下,使用空循环可以简化代码逻辑,尤其是在处理简单的状态机或条件检查时。

3. 避免使用额外的库或工具:
   在资源受限的环境中,程序员可能没有可用的高级同步机制,如事件、信号量或条件变量。在这种情况下,空循环可能是实现等待机制的唯一选项。

4. 调试目的:
   在调试过程中,程序员可能会使用空循环来模拟延迟或强制程序在某个点上暂停,以便观察程序的行为。

5. 特定算法的实现:
   在某些算法中,如某些类型的搜索算法或优化算法,可能需要在找到解决方案之前不断迭代。在这些情况下,空循环可以用来表示这种迭代过程。

6. 依赖于特定硬件或操作系统的行为:
   在某些情况下,硬件或操作系统的行为可能依赖于 CPU 的忙等待。例如,某些硬件中断可能需要 CPU 在特定时间内保持忙碌状态。

7. 教育目的:
   在教学或演示中,空循环可以用来展示循环结构的基本用法,或者用来说明循环的工作原理。

8. 遗留代码维护:
   在维护旧的代码库时,程序员可能会遇到使用空循环的情况。在不改变现有逻辑的情况下,他们可能会保留这些循环,尤其是在不清楚替代方案的长期影响时。

9. 特定编程语言或环境的限制:
   在某些编程语言或环境中,可能没有提供更高级的同步或等待机制,因此空循环成为唯一的选择。

10.误解或缺乏经验:
    有时,程序员可能由于对更好实践的不了解或缺乏经验而使用空循环。

书上的例子的是为了特定算法的实现(为了跳过整数输入)

6.3 用关系运算符和表达式比较大小 

while循环经常依赖测试表达式作比较,这样的表达式被称为 关系表达式(relation expression),

出现在关系表达式中的运算符叫做关系运算符(relation operator)。

  在while语句中使用 关系表达式:

 while( number < 6) 
 
{
    printf("Your number is too small.\n");
    scanf("%d",&number);
 
}

while( ch !='$')
{
    count++;
    scanf("%c",&ch);
}

while (scanf("%f", &num) ==1)
  sum += num;

while语句的关系表达式也可以用于比较字符,比较时使用的是机器字符码

虽然关系表达式也可以比较浮点数,但是要注意:比较浮点数时,尽量只使用<和>

6.3.1 什么是真

简单来说,1是真,0是假

6.3.2 其他真值

所有的非零值都被视为真,只有0被视为假

6.3.3 真值的问题

误用=会导致无限循环

#include <stdio.h>

int main(void)
{
    long num;
    long sum = 0L;
    int status;

    printf("Please enter an integer to be summed");
    printf("(q to Quit)");
    status = scanf("%ld", &num);
    while (status = 1)
    {
        sum = sum + num;
        printf("Please enter next integer (q to Quit):");
        status = scanf("%ld", &num);
    }
    printf("Those integers sum to %ld.\n", sum);
    return 0;
}

循环形成过程:

1. scanf 在读取输入时,如果读取指定形式的输入失败,就会把无法读取的输入留在输入池中。
2. 当它把 q 当作整数读取失败后,q 就留在了输入池。
3. 下次循环时,scanf 会从输入池上次读取失败的位置(也就是 q 所在的位置)继续读取,导致再次失败,进而形成了无限循环。

6.3.4 新的_Bool类型

  •   _Bool 是 C99 标准引入的布尔类型,通常只占用 1 个字节的内存。
  •  bool 是 C++中的布尔类型,在不同的编译器和系统中,其占用的内存大小可能有所不同,但通常也是 1 个字节。

C99提供了stdbool.h头文件,该头文件让bool成为_Bool的别名,而且把true和false分别定义为1和0的符号常量。用了该头文件后写出的代码可以与C++兼容

这也是为什么C语言里_Bool类型我们很少用,反而是C++的bool我们爱不释手的原因

6.3.5 优先级和关系运算符        

6.4 不确定循环和计数循环

#include <stdio.h>

int main(void)
{
    const int NUMBER = 22;
	int count = 1;
	while (count <= 10) {
		printf("count=%d\n", count);
		count++;
	}
	return 0;
}

6.5 for循环

for的形式:

for (initialize; test; update )

  statement

for循环的三个表达式 通常是第一个给计数器赋初始值,第二个表达式表示计数器范围
第三个表达式递增计数器。
实际使用可以灵活的利用三个位置的特点,
第1个表达式只在执行循环嵌执行一次,
第2个表达式可以是任何条件
第3个表达式是可以每次递增、递减、加倍等。
三个表达式可以空缺(但是分号不能缺)

每一个表达式的副作用(如:递增变量)都发生在对下一个表达式求值之前

利用for的灵活性

  • 可以使用递减运算符来递减计数器
  • 可以让计数器跨数递增
  • 可以用字符代替数字计数(ASCII)
  • 除了测试迭代次数外,还可以测试其他条件
  • 可以让递增的量几何增长,而不是算术增长
  • 第3个表达式可以使用任意合法的表达式
  • 可以省略一个或者多个表达式(但不能省略分号),只要在循环中包含能结束循环的语句即可
  • 第1个表达式不一定是给变量赋初值,也可以使用printf()。记住,在执行循环的其他部分之前,只对第1个表达式求值一个或者执行一次
  • 循环体中的行为可以改变循环头中的表达式

6.6 其他赋值运算符(运算符优先级表里有)

6.7 逗号运算符

,逗号运算符扩展了for循环的灵活性,以便循环头中包含更多的表达式jj,
如:
for(ounces = 1, cost = FIRST_OZ; ounces <= 16; ounces++, cost += NEXT_OZ)
    printf("%5d $%4.2f\n", ounces, cost/100.0 );

它保证了被它分隔的表达式从左到右求值(换言之,逗号是一个序列点,所以逗号左侧项的所有副作用都在程序执行逗号右侧项之前发生)

6.8 出口条件循环 do while (先做一次,再检查条件)

保证了至少执行循环体中的内容一次

do 

  statement

while( expression);


6.9 如何选择循环

主要考虑是入口循环还是出口循环,
出口循环选do while , 入口循环for或while

6.10 嵌套循环


循环内的语句也是循环时,就形成嵌套循环。

以简单的二层嵌套为例,外层循环每执行一次,里面的语句(包括循环)就执行一遍。

6.11 数组简介

数组:是按顺序存储的一系列类型相同的值,通过整数下标访问数组中单独的项或元素

用于识别数组元素的数字被称为下标、索引、偏移量。


6.12 使用函数返回值的循环示例

6.12.1 math.h库——pow()幂函数

#include<stdio.h>
#include<math.h>
int main()
{
    int a,b,ret;
    if(scanf("%d %d",&a,&b)==2){
      ret = pow(a,b);    //求出a的b次方
      printf("%d", ret);
    }else printf("输出有误");
    return 0;
}

这里复现时可能会出现问题原因:

底数 a为负数并且指数 b 不是整数,将会导致 domain error 错误.
底数 a和指数 b都是 0,会导致 domain error 错误.
底数 a是 0,指数 b 为负数,会导致 domain error 或 pole error 错误.

PS:如果使用 scanf("%d %d",&a,&b) 作为取得数值途径,可能会因为scanf()函数的特性(第四章有讲)而出错(如:b拿不到数组等等)

6.12.2 使用带返回值的函数

在 C 语言中,`return` 语句用于从函数返回一个值给调用者,或者从函数中提前退出。`return` 语句的基本用法取决于函数的返回类型:

1. 返回值:


   如果函数声明了返回类型(除了 `void`),则 `return` 语句必须返回一个与函数声明的返回类型相匹配的值。

  ```c
   int add(int a, int b) {
       return a + b; // 返回两个整数的和
   }
   ```

2. 无返回值:


   如果函数的返回类型是 `void`,则 `return` 语句不返回任何值,它只是从函数中退出。

   ```c
   void printHello() {
       printf("Hello, World!\n");
       return; // 从函数中退出
   }
   ```

3. 从条件语句返回:


   `return` 语句可以在 `if`、`else if`、`else` 或 `switch` 语句中使用,用于根据不同的条件返回不同的值。

   ```c
   int max(int a, int b) {
       if (a > b) {
           return a;
       } else {
           return b;
       }
   }
   ```

4. 提前退出函数:


   在循环或条件语句中使用 `return` 可以提前退出函数。

  ```c
   int find(int *array, int size, int value) {
       for (int i = 0; i < size; i++) {
           if (array[i] == value) {
               return i; // 找到值,返回索引
           }
       }
       return -1; // 未找到值,返回-1
   }
   ```

5. 返回字符串:


   对于返回字符串的函数,通常返回一个指向字符数组的指针。

   ```c
   char* getGreeting() {
       return "Hello, World!";
   }
   ```

6. 返回多个值:


   虽然 C 语言的函数不能直接返回多个值,但可以通过返回一个结构体或使用指针参数来间接返回多个值。

   ```c
   typedef struct {
       int x;
       int y;
   } Point;

   Point getOrigin() {
       Point p = {0, 0};
       return p; // 返回一个点的坐标
   }
   ```

7. 返回错误码:


   在很多函数中,`return` 语句用于返回错误码,以指示函数是否成功执行。

 ```c
   int openFile(const char* filename) {
       FILE *file = fopen(filename, "r");
       if (file == NULL) {
           return -1; // 打开文件失败,返回错误码
       }
       return 0; // 成功打开文件
   }
   ```

使用 `return` 语句时,应该确保返回的值与函数的声明相匹配,并且在函数的控制流结束前正确地返回。如果函数应该返回一个值,但没有使用 `return` 语句,或者返回了一个错误的类型,编译器可能会发出警告或错误。
 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值