顺序结构呢?不就是从上到下一行一行执行吗?这有啥好讲的?
但你要是这么想,就太小看它了。 我见过太多人,循环和分支学得挺溜,一到写完整程序就翻车——变量声明的位置不对、输入输出的格式控制符写错、运算顺序搞反了、数据算出来全是整数除法截断的结果。这些问题出在哪儿?都出在对顺序结构的基础不扎实上。
说白了,顺序结构是所有程序的地基。循环和分支决定了程序“怎么拐弯”,而顺序结构决定了程序“怎么走路”。路都走不稳,拐弯再花哨也没用。
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语言标准规定:在一个表达式里多次修改同一个变量,结果是不确定的。 顺序结构里尽量别写这种“聪明”的代码,拆成多行写,清楚又安全。
五、最后说几句
顺序结构确实简单,但它是所有程序的骨架。你把它学透了,就能理解“程序就是一步一步干活”的本质。分支和循环都是在这个骨架的基础上长出来的“控制结构”——它们控制的是“哪一步执行、哪一步不执行、哪一步重复执行”,但每一步内部还是顺序的。
练到什么程度算过关? 给你一个简单的需求,你能做到:知道变量在什么时候声明、输入输出格式写得一次就对、运算顺序不会搞反、数据类型转换不出错、代码从上到下读起来逻辑通顺。这就够了。
别觉得顺序结构不配单独写一篇文章。地基不牢,上面盖啥都不稳。把这关过了,后面循环和分支你才能用得更踏实。打开编译器,把你脑子里的“我想先干啥、再干啥、最后干啥”翻译成代码,让计算机一步一步跟着你走,这就是顺序结构的全部秘密。