C语言教程-8-跳转控制和嵌套


title: C语言教程-8-跳转控制和嵌套
tags: [C]
categories: C语言教程
description: 嵌套导致代码更加复杂?尝试直接跳出?

什么是跳转控制

有时候,我们需要代码直接从一个位置直接转到另一个特定的位置继续执行,此时,我们就需要进行跳转.

例如,我们在执行某个循环时,突然到达了某个边界条件,需要立即停止循环(或者没有必要进行余下的循环),此时我们就可以直接跳出循环.

再例如,我们可能需要在同一个函数内进行一个大跨度的回溯,我们如果开循环的话,整个代码都要放在一个代码块里,如果不方便的话,我们就可以直接无条件跳转到前面.

C语言跳转控制相关的语句

break语句

首先来看一下标准的描述:

image-20230928224409360

break语句,可以理解为"跳出"语句,顾名思义,用于跳出一个循环体switch的语句.

事实上,我们在前面讲解switch语句的时候就已经使用到break语句,但是当时只是知道能用它来结束一个switch语句.

换句话说,break语句用于跳出一个代码块(一个复合语句),但是这个代码块仅限于循环体switch语句内.并且,break语句会完全终止一个循环,无论循环语句中有什么需要执行的部分,都不再执行,直接跳转到整个循环后面紧邻的第一条语句继续执行.

重要的一点是,break只能跳出它直接所在的循环,而不能连续跳出,也正如标准所述:break语句不能用于打破多重嵌套循环.至于嵌套循环,我们很快会讲解到.

我们做一个简单的例子来使用一下break语句:

例题:

输入一个正整数n,判断其是否为素数(素数为一个大于1的自然数,除了1和它本身外,不能被其他自然数整除).

解决:我们从定义出发,很容易想出一个暴力的做法—那就是从2到n-1依次判断是否为n的因数,如果中间有任何一个数是因数,则这个数n立即被判定为合数.

代码:

#include <stdio.h>
int main() {
	int n, is_prime = 1;
	scanf("%d", &n);
	for (int i = 2; i <= n - 1; ++i) {
		if (n % i == 0) {
			is_prime = 0;
			break;
		}
	}
	if (is_prime)
		printf("yes");
	else
		printf("no");
	return 0;
}

实际上可以有更加高效/简单的写法,但是为了说明问题,这里采用了这样的写法.

这个代码使用了一个is_prime的int变量作为标志,用于记录最终结果—n是否为素数.

我们开一个i从2到n-1的循环,将循环变量i作为n可能的因数,去和n做除法,如果判断出能够整除,就说明当前的i是n的因数.

然后,根据我们的算法,只要i在2~n-1中有任何一个是n的因数,就说明n不是素数,此时,后面的循环已经没有必要执行了,因为这里已经判断出n不是素数了,就不需要再继续了.

那么我们在if成立后,先把is_prime变量设置为0(也就是标志着n不是素数),然后使用一个break语句来直接结束这个循环,继续后面的代码.最后,我们使用一个if…else来进行判断,对应输出yes或no即可.

其中需要注意的是,我们使用到了一个%运算符,这个运算符叫求模运算符,用于求一个数除另一个数的余数,这两个数要求必须是整数.例如7%4的结果就是3;5%1的结果就是0.有关求模将会在讲解运算符时进行详细说明.

continue语句

有时候,我们需要的仅仅是结束当前的一次循环,而不是结束整个循环语句,那么,我们需要使用continue语句来实现.

需要注意的是,所谓"结束",仅仅指的是结束循环体,这意味着for(表达式1;表达式2;表达式3)并不受影响,我们在结束当前的一次循环时,意味着continue后面直到循环体结束的所有代码不再执行,转而直接开始下一次循环,同时,表达式3不受任何影响,它仍然需要执行一次再进行下一次循环的表达式2的判断.

同样来看一下标准的描述:

image-20230928231836072

就是这么简单,我们同样以一个例题来举例子:

例题:

输出100以内所有不能被3整除的正整数.

解决:很简单,只需要剔除能被3整除的数即可,正向思维的话,我们直接把不能被3整除的数输出即可,但是这里作为例子,我们反过来,如果遇到能被3整除的数,则跳过该次循环.

代码:

#include <stdio.h>
int main() {
	for (int i = 1; i <= 100; ++i) {
		if (i % 3 == 0) // 如果i除3的余数为0,也就是i能被3整除
			continue; // 则跳过这次循环之后的所有代码,这里也就是跳过输出
		printf("%d ", i); // 否则输出i
	}
	return 0;
}

代码就是这样,其中,再说明一下,我们把int i=1,也就是i的声明和初始化都放到for的第一个表达式中了,这样是没有问题的,但是这意味着这个for循环一旦结束,变量i就不能使用了,换句话说i在for循环的这个块作用域内.这涉及到变量的作用域,将会在后面进行详细的讲解.

goto语句

除了上面的两种跳转语句,我们还有一种更加直接的goto语句,可以直接跳转到同函数内的任意位置,所以它也叫无条件跳转语句.

这是标准的描述:

image-20230929000327639

初学,我们无需关心那么多,我们只需要知道,goto语句和其所控制的标号只能在同一个函数内.

那么,一个完整的goto语句使用需要哪些内容呢?我们首先需要一个或多个标号.

标号

同样先来看标准的描述:

image-20230929001604610

也就是说,标识符:,case 常量表达式:,default :这3种实际上都是标号,他并不是独立的,实际上,他是对于某条语句进行了一个标号.

在switch语句中,我们习惯把case 常量表达式:写在单独的一行,但是实际上,它是下一行第一条语句的标号:

#include <stdio.h>

int main() {
    int score;
    scanf("%d", &score); // 用于输入
    switch (score) {
        case 1:
            printf("开始游戏");
            break;
        case 2:
            printf("查看排名");
            break;
        case 3:
            printf("设置");
            break;
        case 4:
            printf("退出");
            break;
        default:
            printf("输入错误");
    }
    return 0;
}

在这个程序中,每一个case 常量表达式:和最后的default :都是后面紧随的printf(...);这条语句(…为省略中间的内容)的标号!

使用goto语句进行跳转

那么goto语句实际上使用的是前面3中标号的第一个,也就是标识符:,有了标号,我们就可以直接这样使用:

#include <stdio.h>
int main() {
	printf("第1个输出\n");
	printf("第2个输出\n");
	goto first;
	printf("第3个输出\n");
	first:
	printf("第4个输出\n");
	return 0;
}

运行结果为:

image-20230929002235202

我们在程序中为printf("第4个输出\n");提供了一个标号first,我们在输出"第3个输出\n"之前使用goto语句进行跳转,跳转到后面继续执行,如此,第3个printf()函数的调用就被跳过了.

注意:

goto语句可以说的上是非常简单粗暴,如果各位学习过汇编语言,就会发现goto语句和汇编中的loop非常相似!

但是简单粗暴意味着,它很容易破坏程序的结构,说简单点就是,如果一个程序在运行中来回不停地使用goto进行到处的跳转,那么很可能导致整个程序的运行逻辑乱七八糟的,毫无章法.

所以,C语言中的goto语句常常被许多人诟病,从而强烈建议不要使用该语句.

至于本人,我对goto语句的看法是:只要合理利用,就是好的.依然是那句话:C语言提供给了我们极大的自由度,至于如何利用好这种自由度,就看程序设计者本人的能力了.

那么goto语句,最实用的一个用途就是一次性跳出多层嵌套的循环,这将在下面进行讲解.

return语句

标准的描述:

image-20230929004440108

关于return语句,应该把他放在函数的部分进行讲解,到目前为止,我们所有的程序都只用了一个函数:main函数就完成了任务,当程序越发复杂,我们需要把功能独立出来,这时候函数就派上用场了.

在此之前,我们只使用main函数来完成所有的功能.

那么return语句,就是用于结束一个函数,并返回一个值作为这个函数的返回值.

返回值很好理解,我们之前说过,C语言的函数和数学意义上的函数完全一致,那么如果我们有y=f(x),最终计算出来的y就是函数的值,也就是函数值,return语句就是用于返回这个最终计算的值的.

在main函数中,我们默认使用return 0;返回一个0作为main函数的返回值,在main中,返回值为0代表这个程序正常结束:

#include <stdio.h>
int main() {
	printf("hello world");
	return 0; //结束main函数并返回0
}

那么为什么是整数0呢?我们之前提到过,int main()中第一个代表这个函数的返回值类型,那么这里的int说明main的返回值应该是一个int类型的值,那么常量0就是一个int类型的常量.

另外,如果一个函数的返回值类型是void,即返回值为空,那么必须写return;来返回,而不能带有任何表达式.

这里先简单说明一下,至于return语句的细节会在后面详细讲解.

​ —WAHAHA 2023.9.28

第一个难点—循环嵌套

什么是嵌套

嵌套顾名思义,就是一个语句内套着另一个语句.嵌套并不是说仅仅给循环,任意语句均可以互相随意嵌套以完成各种功能.

如果对应到if语句,那么就是一层条件满足后继续进一步判断下一层条件.

对应到循环,就是循环嵌套.也就是一层循环中套着另一层循环.

循环嵌套写起来十分简单,只需要简单的将一个循环放在另外一个循环里面即可.

例如:

#include <stdio.h>
// 这段代码的作用是输出 0~99
int main() {
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 10; j++) {
            printf("%d ", i * 10 + j);
        }
    }
    return 0;
}

虽然上面这个代码的功能仅需要一个循环即可实现,这里为了作为演示,有上面这种替代的写法.

最常用的嵌套—for嵌套

例子1-直角三角形

题目

输出一个直角边长为5的等腰三角形,要求使用循环,输出结果要求如下:

*
**
***
****
*****
分析

我们要输出这个图形,需要从第一行向下一行一行的输出,首先约定,从今以后,我们所有的行,列都从0开始计数.也就是说,逻辑上的第1行,我们称之为第0行.

第0行输出1个’*‘字符,第1行输出2个’*‘,…,第n行输出n+1个’*‘,…,第5行输出6个’*'.

我们可以看出,输出逻辑中有两个关键值一直在变,那就是当前的行号当前这行需要输出的'\*'数.

而且,必须先确定一行,然后再针对这行进行逐个输出,那么很自然地,我们就能想到使用循环嵌套来实现(没想到也没关系,先来看看代码).

代码
#include <stdio.h>

int main() {
    //输出一个直角三角形
    int i, j;
    for (i = 0; i < 5; i++) { // i指定行号,其中的 i++ 完全等价于i=i+1,此时不必有任何疑惑
        for (j = 0; j < i+1; j++) { // j指定该行输出多少个字符'*',其中的 j++ 完全等价于j=j+1,此时不必有任何疑惑
            printf("*"); // 输出一个'*'
        }
        printf("\n"); // 内层循环输出完一行后输出换行准备处理下一行
    }
    return 0;
}
执行过程

执行的过程如下:

1.首先,进入外层的第一次循环,此时i为0,也就是正在处理第0行,然后保持i==0不变,进入第二层循环;

根据我们的公式,第n行需要输出n+1个’*‘,那么第0行需要输出0+1也就是1个’*'.但是我们需要一次性写完所有行的循环,那么这里我们应该把公式放进去,那么外层循环的i就相当于这里的n,内层循环每次要执行i+1次,那么就应该是

for (j = 0; j < i+1; j++)

当第一次内层循环(i一直为0)结束后,屏幕上已经输出了1个’*'—循环了i+1次也就是1次.此时外层第一次循环还没结束,因为还有一个printf("\n");没有执行,执行完这行后,会跳到下一行准备继续输出.

这时屏幕上是这样的:

*
[注意光标在这里,因为已经换行了]

2.外层循环内的一个内层循环和一个printf(“\n”);语句已经执行完,这时i++,变为1,准备第二次循环,也就是准备输出第1行(注意这是图像上的第二行)的2个’*';

根据我们的公式,第1行需要输出1+1也就是2个’*',需要内层循环两次;

for (j = 0; j < i+1; j++)在这时,j被重新赋值为0—也就是说这次内层循环的执行和上一次没有任何关系!接着,j从0到i(也就是1),循环2次,屏幕上输出了2个’*',此时外层第二次循环还没结束,因为还有一个printf("\n");没有执行,执行完这行后,会跳到下一行准备继续输出.

这时屏幕上是这样的:

*
**
[注意光标在这里,因为已经换行了]

3.外层循环内的一个内层循环和一个printf(“\n”);语句已经执行完,这时i++,变为2,准备第三次循环…

如此往复递增…

4.当第5次外层循环执行完毕,屏幕上是这样的:

*
**
***
****
*****
[注意光标在这里,因为已经换行了]

此时i再自增到6,不满足条件,外层循环结束,至此,整个嵌套循环彻底结束.

例子2-左右对称的等腰三角形

题目

输出一个高度为5的左右对称的等腰三角形,要求使用循环,输出结果要求如下:

    *
   ***
  *****
 *******
*********

分析&代码

从例子1我们可以有所体会—我们需要推导出每个循环应该执行的次数(甚至需要考虑从哪个值到哪个值).

这个例子就更进一步,需要我们推出更复杂的式子(没复杂到哪去),我们来考虑,这回按照写代码的思路来引导各位.

1.首先依旧,我们需要输出5行,秉承着一行一行处理的思路,我们仍然选择开一个循环,让i从0~4进行控制这5行,那么我们有:

#include <stdio.h>

int main() {
    //输出一个高度为5的左右对称的等腰三角形,要求使用循环
    int i;
    for (i = 0; i < 5; i++) {
    	
    }
    return 0;
}

2.假设我们已经开始处理第i行—也就是已经开始了某一次循环(注意,这里说的是某一次,而不是第一次,读者去考虑).

那么我们观察图形,第0行需要1个’*‘,第1行需要3个’*‘,第2行需要5个’*',…那么我们就能推出公式:

第 i 行需要 2*i+1 个'\*'

那么很显然,我们需要这样,同时不要忘了最后的换行:

#include <stdio.h>

int main() {
    //输出一个高度为5的左右对称的等腰三角形,要求使用循环
    int i,k;
    for (i = 0; i < 5; i++) {
    	for (k = 0; k < 2 * i + 1; k++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

3.我们运行测试一下,结果发现全部居左了:

image-20230930001507386

​ 问题很容易发现,我们忘记在每一行开头输出特定数量的空格' '了.

4.那么同样去观察规律,同理很容易得出:

如果一共有i行(从0开始计数),那么第j行需要i-j行

例如,我们一共有4行(从0开始计数),那么第2行(也就是图像的第三行)需要4-2也就是2个空格

这样代码就需要补全为这样(注意每次外层循环中,要先进行空格的输出,然后才是’*'的输出):

#include <stdio.h>

int main() {
    //输出一个高度为5的左右对称的等腰三角形,要求使用循环
    int i, j, k;
    for (i = 0; i < 5; i++) {
        for (j = 0; j < 4 - i; j++) {
            printf(" ");
        }
        for (k = 0; k < 2 * i + 1; k++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

那么到现在,我们的程序就完成了.

例子3-任意高度的等腰三角形

题目

同样如例子2,输出一个左右对称的等腰三角形;

要求输入一个正整数n,输出高度为n的等腰三角形.

同样要求使用循环,输入输出结果要求如下:

6
     *
    ***
   *****
  *******
 *********
***********

分析

处理过例子2,这个题很简单,只需要把之前的常量4(代表五行输出—从0开始计数)改为我们输入的变量n即可.

我们加入一个输入n.

我们之前的for (i = 0; i < 5; i++)变为for (i = 0; i < n; i++)

for (k = 0; k < 2 * i + 1; k++)不变

for (j = 0; j < 4 - i; j++)变为for (j = 0; j < n - 1 - i; j++)

注意,这里之所以写n-1-i而不是n-i,是因为我们输入的是图像的高度,而代码中我们从0开始计数!所以需要减1!

代码
#include <stdio.h>

int main() {
    //输出一个高度为n的左右对称的等腰三角形,要求使用循环
    int i, j, k, n;
    scanf("%d", &n);
    for (i = 0; i < n; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            printf(" ");
        }
        for (k = 0; k < 2 * i + 1; k++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

另外,我们发现需要4个变量,未免有点多余,实际上我们仅需要3个:

#include <stdio.h>

int main() {
    //输出一个高度为n的左右对称的等腰三角形,要求使用循环
    int i, j, n;
    scanf("%d", &n);
    for (i = 0; i < n; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            printf(" ");
        }
        for (j = 0; j < 2 * i + 1; j++) { // 这里也使用j
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

之所以能这么写,是因为内层的两个循环是平行的,就这个程序而言,互不影响,因为两个for都需要再开始前进行j=0,所以,我们把原来的k换成j即可.

其他各种嵌套

这里仅仅写一下结构,事实上,完全没有必要,因为实际编程中完全是我们想怎么写,或者说需要怎么写,我们就按照自己的想法去写即可,完全没有任何限制.

因为,嵌套就是一个语句内有其他语句,并没有规定两者间的联系,你甚至可以在一个do...while()循环中嵌套一个switch语句,完全没有任何问题.

我们可以这样:

#include <stdio.h>

int main() {
    for(;;){
        do{
            //...
        }while();
    }
    return 0;
}
#include <stdio.h>

int main() {
    int flag=0;
   	while(1){
        for(;;){
            //...
            if(...){
                flag=1;
                break;
            }
        }
        if(flag==1)
            break;
    }
    return 0;
}

上面仅仅举了两个任意的框架作为例子而已,实际上随意,而且并不是一个循环中只能套一个循环,你可以放入任意数量/排列的语句(包括复合语句)

而且,我们还可以进行3层嵌套,4层嵌套…只要不超出编译器允许的范围…当然,一般嵌套层数超过了4,你可能就需要重新考虑你的代码结构了…因为他不仅看起来不美观,而且往往效率极低…


到此,我们介绍了跳转控制和嵌套的基本思路,具体的掌握还需要各位多去练习,例如上网随便找C语言练习100题之类的,一搜一大把.

---WAHAHA 2023.9.29

注:文章原文在本人博客https://gngtwhh.github.io/上发布。

  • 35
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值