【C语言-顺序结构】啃透C语言的顺序结构,最简单的往往最容易被忽视

开发技术 作者: 精灵星空 SKY315.COM 更新于: 浏览: 195
码字发文不易,转载请注明出处【精灵星空 SKY315.COM】

顺序结构呢?不就是从上到下一行一行执行吗?这有啥好讲的?

但你要是这么想,就太小看它了。 我见过太多人,循环和分支学得挺溜,一到写完整程序就翻车——变量声明的位置不对、输入输出的格式控制符写错、运算顺序搞反了、数据算出来全是整数除法截断的结果。这些问题出在哪儿?都出在对顺序结构的基础不扎实上。

说白了,顺序结构是所有程序的地基。循环和分支决定了程序“怎么拐弯”,而顺序结构决定了程序“怎么走路”。路都走不稳,拐弯再花哨也没用。

C语言在线编程环境:https://sky315.com:8080/c/。

一、顺序结构长啥样?就是一句一句往下走

顺序结构的概念简单到不像个知识点:程序从main函数开始,一句一句按顺序执行,中间不跳转、不循环,直到最后一句结束。

看起来是句废话,但你品一品——你写的每一行代码,在什么时机执行、变量在什么时刻被赋了新值、数据在哪个节点被输入输出,全都由这个“从上到下”的顺序决定。顺序结构本质上是“时间上的先后关系”,而不仅仅是代码排版。

C语言里顺序结构的载体就是语句。语句用分号结尾,一个分号就是一句。常见的几类:

  • 声明语句:int a; 或者 int a = 10;

  • 表达式语句:a = b + c; 或者 i++;

  • 函数调用语句:printf("hello"); 或者 scanf("%d", &a);

  • 空语句:就是一个分号;,啥也不干,偶尔在特殊场景有用

这些东西组合在一起,形成了程序的“流水线”。流水线的顺序就是它们的执行顺序。

二、顺带着把输入输出讲清楚,这是个重头戏

顺序结构里最常打交道的就是输入输出。程序要跟用户交互,靠的就是这个。

printf——格式化输出

printf("格式控制字符串", 输出参数列表);

格式控制符你得会:%d是整数、%f是浮点数、%c是字符、%s是字符串。还有一个容易被忽略的——%%输出百分号本身。

格式控制符还能加修饰:

  • %5d——占5个字符宽度,右对齐

  • %-5d——占5个字符宽度,左对齐

  • %.2f——保留两位小数

  • %8.2f——占8个字符宽度,保留两位小数

这些东西不用死记,多用两次就熟了。但有一个原则必须记住:格式控制符的个数和类型必须跟后面的参数一一对应。 你写了三个%d就传三个整数,写了一个%d一个%f就传一个整数一个浮点数。对不上编译不报错,但运行结果全是乱的。

scanf——格式化输入

scanf("格式控制字符串", 地址列表);

注意了,后面跟的是变量的地址,不是变量本身。所以你要写scanf("%d", &a);,那个&取地址符不能省。很多人第一周学C语言就在这里翻车,忘了写&,程序崩溃或者乱读内存。

scanf的格式控制符跟printf类似,但有几点要注意:

  • %d匹配整数,%f匹配浮点数

  • %c会读取包括空格和换行符在内的任何字符,这有时候会搞出意外——你输入完数字敲的回车,可能被后面的%c读走

  • 在%d和%f格式下,空格、换行、Tab会被自动跳过

处理字符串输入的时候,%s遇到空格就停了,所以读不了一整行带空格的字符串。要读一整行可以用gets(但不安全,已经不建议用了)或者fgets。

getchar和putchar——单个字符的输入输出

这两个简单,一次只处理一个字符。getchar()从键盘读一个字符,putchar(c)输出一个字符。在循环里处理文本的时候特别好使。

三、典型例题挨个讲,理解顺序到底是怎么走的

例子不多,四五个就够了,但每一个都戳在顺序结构的核心点上。

例1:交换两个变量的值——顺序决定了会不会翻车

输入a和b的值,交换它们,再输出。

int a, b, temp;
scanf("%d%d", &a, &b);

temp = a;
a = b;
b = temp;

printf("a=%d, b=%d\n", a, b);

交换变量的经典三步走,顺序绝对不能乱。你写a = b; temp = a; b = temp;试试?a先被覆盖了,temp存的已经是b的值,最后a和b都成了原来的b,数据全丢了。就这么一个顺序问题,程序直接变成“把两个数都变成b”。

这里的temp就是第三只手——两只手要交换手里的东西,必须借助一个临时位置。这个思路在编程里到处都用,顺序就是那个“先腾出手来”的关键。

例2:计算圆的面积和周长——运算顺序里的门道

输入半径,算面积和周长。

float r, area, perimeter;
const float PI = 3.14159;

scanf("%f", &r);

area = PI * r * r;
perimeter = 2 * PI * r;

printf("面积=%.2f, 周长=%.2f\n", area, perimeter);

看似简单,但运算顺序和数据类型是个暗坑。PI * r * r是从左到右计算的,先PI * r算出一个浮点数,再乘r,结果是浮点数。但如果写成r * r * PI,同样的结合顺序,结果一样。

那如果把const float PI = 3.14159;换成#define PI 3.14159,也一样。但你要是写成int PI = 3;,那PI * r就是整数乘浮点数,结果被转成浮点数再继续算,虽然最后结果类型还是float,但精度从声明那一刻就丢了。每一步计算的数据类型都影响最终结果,这个逻辑顺序你要能追得上。

例3:把秒数换算成时分秒——整除和取余的时机很重要

输入一个总秒数,输出它对应的时、分、秒。

int total, hours, minutes, seconds;
scanf("%d", &total);

hours = total / 3600;
minutes = (total % 3600) / 60;
seconds = total % 60;

printf("%d小时%d分%d秒\n", hours, minutes, seconds);

这个题的顺序体现在“层层剥洋葱”上。先整除取小时,剩下不足一小时的部分用取余拿到;再对余数做整除得到分钟;最后剩下的就是秒。你如果把运算顺序调换一下,比如先取分钟再取小时,得到的结果就完全不对了。

这种“先大后小,逐层分解”的思路,在日期计算、货币换算、单位换算里到处都是。顺序本身其实就是算法的一部分。

例4:输入的坑——那个幽灵般的换行符

写一个程序,先读一个整数,再读一个字符。

int num;
char ch;
scanf("%d", &num);
scanf("%c", &ch);
printf("num=%d, ch=%c\n", num, ch);

你输入123然后敲回车,结果ch得到的不是你想输入的字符,而是那个换行符\n。为啥?因为scanf("%d")读走了123,但把回车留在了输入缓冲区里。紧接着%c一看,缓冲区里有东西,读走,回车,完事,根本不给你输入字符的机会。

怎么解决?在%c前面加个空格:scanf(" %c", &ch);。这个空格会跳过缓冲区里所有空白字符(空格、换行、Tab),直到读到非空白字符为止。

同样的,如果你连续读两个字符,中间没有空格隔开,%c也会把空格当成有效输入。输入数据的格式要和scanf的格式控制字符串严格对应,这个顺序关系看着简单,实际调试起来够你折腾的。

例5:三位数的数字拆分——位权运算的顺序

输入一个三位数,输出它的百位、十位、个位。

int num, a, b, c;
scanf("%d", &num);

a = num / 100;
b = (num / 10) % 10;
c = num % 10;

printf("百位=%d, 十位=%d, 个位=%d\n", a, b, c);

整数除法自动截断,配合取余运算,可以把一个数字按位拆开。这里的顺序是:先除100得百位,再除10取余得十位,最后直接取余得个位。

这道题的变形:如果输入的是任意位数的整数,想倒序输出,怎么做?核心思路还是整除和取余交替使用,每次取个位、丢掉个位,直到数变成0。这个“循环”的思路就是建立在顺序操作上的,你先把单次操作写清楚,再套循环。

例6:用printf搞点花样——顺序输出里的美学

printf("姓名:%s\n", name);
printf("年龄:%d岁\n", age);
printf("成绩:%.1f分\n", score);

printf("========================\n");
printf("|  姓名  | 年龄 | 成绩 |\n");
printf("========================\n");
printf("| %-6s | %4d | %6.1f |\n", name, age, score);
printf("========================\n");

输出顺序就是你写的顺序,一句接一句。但如果你想让输出对齐,就得在格式控制符里做文章。%-6s是左对齐占6个字符宽度,%4d是右对齐占4位,%6.1f是占6位保留一位小数。

这些格式控制符的用法是顺序结构的日常操作,输出是循序渐进的,每一行都在前一行之后出现,你要想清楚用户在屏幕上看到信息的先后顺序,把你的逻辑按这个顺序组织好。

四、顺序结构里那些防不胜防的小坑

坑1:变量声明和使用的顺序

int a = b + 5;
int b = 10;

这能编译通过吗?不能。变量声明在使用之前必须已经存在。C语言里变量声明的位置有一定的灵活性(C99之后可以在语句中间声明),但先声明后使用的原则从来没变过。

坑2:运算符优先级和结合性

a = b = c = 5;是从右往左赋值的,先c=5,再b=c,再a=b。a = 5 + 3 * 2;是先乘后加。这些顺序如果搞混了,算出来的值跟你预期的不一样。C语言运算符的优先级表虽然不用全背,但常见的乘除优先于加减、逻辑运算的优先级顺序你得心里有数,不确定就加括号,括号的优先级最高,加上了顺序就明确了。

坑3:整数除法截断

int a = 5, b = 2;
float c = a / b;   // c = 2.0,不是2.5

整数除法算出来还是整数,小数部分直接扔掉,然后再转成浮点数。想得到2.5?至少把其中一个操作数转成浮点数:float c = (float)a / b; 或者 float c = 5.0 / 2;。

这个“先算后转”的顺序决定了结果。类型转换发生的时机比类型本身更重要。

坑4:增量运算符的副作用

int a = 5; int b = a++ + ++a; 这种代码是“未定义行为”,不同编译器结果可能不一样。C语言标准规定:在一个表达式里多次修改同一个变量,结果是不确定的。 顺序结构里尽量别写这种“聪明”的代码,拆成多行写,清楚又安全。

五、最后说几句

顺序结构确实简单,但它是所有程序的骨架。你把它学透了,就能理解“程序就是一步一步干活”的本质。分支和循环都是在这个骨架的基础上长出来的“控制结构”——它们控制的是“哪一步执行、哪一步不执行、哪一步重复执行”,但每一步内部还是顺序的。

练到什么程度算过关? 给你一个简单的需求,你能做到:知道变量在什么时候声明、输入输出格式写得一次就对、运算顺序不会搞反、数据类型转换不出错、代码从上到下读起来逻辑通顺。这就够了。

别觉得顺序结构不配单独写一篇文章。地基不牢,上面盖啥都不稳。把这关过了,后面循环和分支你才能用得更踏实。打开编译器,把你脑子里的“我想先干啥、再干啥、最后干啥”翻译成代码,让计算机一步一步跟着你走,这就是顺序结构的全部秘密。