前语1:此专栏为自己的C言语学习记载整理。假如潦草的学习记载为第1版的话,这份整理和重构后的笔记便是第2版了。按道理这份笔记仅仅给我自己看的,但假如有除了我以外的其他读者(比方您),乃至您能在这儿有所收成的话,那真是万分荣幸。
前语2:此版内容来自B站Up主鹏哥C言语的《C言语:从入门到精通》、K. N. King的《C言语程序设计:现代方法》以及自己的弥补和拓展。
1. 各类句子
C言语是结构化的程序设计言语,而结构化指的是实际世界事物开展逻辑的笼统,即次序结构、挑选结构和循环结构。
C言语的大多数句子归于以下3大类。
-
分支句子
-
循环句子
-
跳转句子
此外还有复合句子(compound statement,把几条句子组合成一条句子)和空句子(void statement,不履行任何操作)。
学习这些句子之前需要了解是句子什么。
句子是程序运转时履行的指令。C言语规定每条句子都要以分号完毕(但也存在少许破例),因为句子能够接连占用多行,需要用分号来确定其完毕方位——而指令通常只占用一行,因而不需要分号完毕。
因而能够简略理解为:在C言语中,由一个分号;
隔开的便是一条句子。
2. 分支句子/挑选句子
在一组可选项中挑选一条特定的履行途径的句子,称为分支句子/挑选句子(selection statement)。
2.1 if句子
语法结构如下:
第一种:
if(表达式)
句子;
第二种:
if(表达式)
句子1;
else
句子2;
第三种(多分支/级联式):
if(表达式1)
句子1;
else if(表达式2)
句子2;
else
句子2;
当表达式成果为真时,其对应的分支就会履行。
那么C言语中如何表达真假呢?在C89规范中,非0便是真(包括-1等负数),0便是假。
(C99中供给了_Bool
型和能够供给bool
宏的头<stdbool.h>
,但因为本文主要讲C89规范,因而先不进行讲解。实际上,C99中的true
和false
也仅仅运用了无符号整型的1
和0
罢了。)
因而括号中的表达式能够运用各种句子,包括联系操作符等来计算。
弥补1:
-
级联式
if
句子不是新的句子类型,而是一般的if
句子,只不过碰巧有别的一条if
句子作为else
下的子句。 -
假如要一个分支下要履行多条句子,则应该运用代码块
{}
来组成复合句子。
留意:代码块{}
后不需要加分号;
。
弥补2:悬空else
问题
int a = 0;
int b = 2;
if (a == 1)
if (b == 2)
printf("yes\n");
else
printf("no\n");
上面这段代码的else
对应的是哪个if
呢?
C言语遵循的规则是,else
子句应该归于离它最近的且还未和其他else
匹配的if
句子。
因而这儿的else
会被判别为第二个if
句子的分支。
这表明,想要使分支句子有清晰的逻辑和结构,则最好加上代码块{}
。
别的,某些if...else...
表达式能够被条件表达式(三元操作符)代替。
2.2 switch句子
2.2.1 介绍
switch
也是一种分支句子,常常用于多分支的状况,比方将表达式和一系列值进行比较,从中找出当时匹配的值。
其结构为:
switch(整型表达式) //留意,这儿有必要是 整型,int long 乃至 char 都行,但 float 不能够
{
句子项;
}
句子项里是一些case
句子,如下:
case 整型常量表达式:
句子;
但在switch
句子无法直接完成分支,调配break
运用才干真实完成分支效果,即:
C
switch(整型表达式) //操控表达式
{
case 整型常量表达式: //分支标号,留意,这儿有必要是 整型常量
句子;
break;
}
弥补:整型常量表达式能够运用char
的原因是字符在底层存储时运用的是ACII码值。
switch语
句什么时候能用到呢?
举个比方,假定咱们需要输出一周的每一天,此刻用级联式if
句子来书写过于复杂,因而能够挑选运用结构清晰明晰的switch
句子:
switch (day)
{
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五\n");
break;
case 6:
printf("星期六\n");
break;
case 7:
printf("周日\n");
break;
}
而且,switch
句子还能够有如下的特别写法,将多个分支标号放置在同一组句子的前面:
switch (day)
{
case 1:
case 2:
case 3:
case 4:
case 5:
printf("工作日\n");
break;
case 6:
case 7:
printf("休息日\n");
break;
}
乃至能够写成这样:
switch (day)
{
case 1: case 2: case 3: case 4: case 5:
printf("工作日\n");
break;
case 6: case 7: printf("休息日\n");
break;
}
因为非强制要求的语法格局并不影响C言语自身的编译,非强制要求的格局仅仅一种代码风格。
当表达的值与一切case
标签的值都不匹配,则会越过一切句子,但假如不想忽略这部分不匹配标签的表达式的值时,则能够在列表中添加一条default
子句(可选)。
default
子句能够写在任何一个case
标签能够呈现的方位,当switch
表达式的值不匹配一切case
标签的值时,default
子句后边的句子就会履行,所以每个switch
句子中只能呈现一条default
子句。
C言语不答应有重复的分支标号,但对分支的次序没有要求,因而default
句子能够呈现在句子列表的任何方位,而且句子流会像贯穿一个case
标签相同贯穿default
子句。
但习惯上一般将default
子句放在最终:
switch(整型表达式)
{
case 整型常量表达式1:
句子1;
break;
case 整型常量表达式2:
句子2;
break;
defalut:
句子3;
break;
}
弥补:
-
switch
答应嵌套运用。 -
为了避免误写相似
if (a = 1)
这样的句子,能够写为if (1 == a)
。 -
switch
句子除了比级联式if
句子易读之外,往往比后者履行速度更快,特别是在有许多状况要判定的状况下。
2.2.2 break句子的作用
switch
句子中需要break
句子的原因是,switch
句子实际上是一种“根据计算的跳转”。对操控表达式求值时,操控会跳转到与switch
表达式的值相匹配的分支标号处。
而分支标号仅仅一个说明switch
内部方位的标记——这种直接的跳转也是switch
往往比级联if
更快的原因,因为不必作正确分支之前或许存在的多次判别。
履行完正确分支中的句子后,假如没有break
来使流程跳出switch
句子,那么就会主动履行下一个分支而无视分支标号是否契合操控表达式里的判别,然后或许导致程序犯错。
这便是break
句子被需要的原因。
注:最终一个分支理论上不需要break
句子,但为了避免将来自己或别人追加分支数目时呈现问题,仍是写上为好。
3. 循环句子/重复句子
重复履行一条或一组句子的句子,称为循环句子/重复句子(loop statement)。
循环句子一般包括操控表达式(controlling expression)和循环体(loop body)。前者用于判别是否持续循环,后者是被循环的内容。
3.1 while
当条件满意的状况下,if
句子后的句子会履行,但只履行一次。但有些时候需要程序履行多次,此刻就能够运用while
句子。
其语法结构如下:
while(表达式) //操控表达式
循环句子; //循环体
举个比方,当咱们需要打印1到10的数字时,能够这样做:
int i = 1; //初始化
while (i <= 10) //判别部分
//当i添加到11时,判别表达式将计算为0(假),判别不成立,不再进入循环
{
printf("%d\n", i );
i++; //调整部分
}
同样的,咱们随时能够用break
中止整个循环,别的也能够用continue
越过本轮循环:
int i = 1;
while (i <= 10)
{
if (9 == i)
{
i++;
break;
}
if (5 == i)
{
i++;
continue;
}
printf("%d\n", i); //1 2 3 4 6 7 8
i++;
//上面两行能够结合,写为 printf("%d\n", i++);
}
注:当循环体只要一条句子时,跟if
句子相同,能够将花括号{}
去掉。
弥补1:
-
getchar()
:用于从流中或标准输入(键盘)中读取一个字符,并回来读到的字符,假如读到过错或者文件完毕,则回来一个EOF
(end of file,是文件的完毕标志,-1)。- 比方输入一个
A
,则相当于把A\n
放入缓冲区中。 - 输入ctrl + z时
getchar
读取到完毕。 -
getchar
读取的实际上是ASCII码值(或EOF
),所以读取到的字符能够有类型。
- 比方输入一个
-
putchar()
:输出一个传入的字符。
运用示例:
int ch = 0;
while ((ch = getchar()) != EOF)
putchar(ch); //输出每一个输入的字符,其实便是循环读取然后输出
弥补2:getchar
运用场景,需要用户输入和承认暗码:
char password[20] = { 0 };
printf("请输入暗码:>");
scanf("%s", password); //这儿不必&取地址的原因是数组中存储的原本便是一系列地址
printf("请承认暗码(Y/N):>");
//清理缓冲区中的多个字符,直到缓冲区为空
int tmp = 0;
while ((tmp = getchar()) != '\n')
{
;
}
int ch = getchar();
if ('Y' == ch)
{
printf("承认成功\n");
}
else
{
printf("承认失利\n");
}
弥补3:运用场景,只打印数字
int ch = 0;
while ((ch = getchar()) != EOF)
{
if (ch < '0' || ch > '9')
continue;
putchar(ch);
}
3.2 do while
do
句子和while
句子本质上是相同的,只不过前者的操控表达式是在每次循环玩循环体之后进行判定的。
其语法结构为:
do
循环句子;
while(表达式);
int i = 1;
do
{
printf("%d ", i);
i++;
} while (i > 1 && i < 10);
不过do...while
适用场景有限,较少被运用。
此外,break
和continue
同样能够在其中运用。
3.3 for
for
句子(也能够叫for
循环)是最被频繁运用的一种循环,尽管结构上比前两种循环多了些条件,但也因而变得非常灵活,可读性也更高。
其语法结构为:
for(表达式1; 表达式2; 表达式3)
循环句子;
表达式1为初始化部分,用于初始化循环变量;表达式2为条件判别部分,用于判别循环什么时候中止;表达式3为调整部分,用于循环条件的调整。
示例:打印数字1-10
int i = 0;
for (i = 1; i <= 10; i++)
printf("%d ", i); //因为循环体只要一条,能够省去花括号
相比while
循环,for
循环将初始化、中止判别和条件调整都集合到一起,易于阅读和修改。
此外,break
和continue
也能够在for
循环中运用,效果基本相同,但for
中的continue
会主动跳到调整部分,而while
里则没有这种功能。
建议:
-
不在
for
循环体内修改循环变量,避免for
循环失掉操控; -
for
句子的循环操控变量的取值选用“前闭后开区间”写法,相似i < 10
。
弥补:for
循环的变种
//1. 三个部分能够恣意省掉,但判别部分省掉后恒为真,会陷入死循环
for (;;) //留意,句子能够省掉,但分号不能省掉
printf("...\n");
//2. 能够有多个操控变量和多个判别条件
int x, y;
for(x = 0, y = 0; x < 2 && y < 5; ++x, y++)
printf("hallo\n");
4. 跳转句子
现已提到过的break
、continue
和return
便是跳转句子(jump statement),它们导致无条件地跳转到程序中的某个方位。
在循环句子中添加跳转句子的作用是,程序员能够将程序设计为现已满意某种条件的状况下主动跳出循环。
除了以上三个句子外,C言语中还供给了能够随意乱用的goto
句子和标记跳转的标号。
语法结构:
//标号句子
标识符:
句子;
//goto句子
goto 标识符;
理论上goto
句子是没有必要运用的,实践中没有goto
句子也能够很容易地写出代码:
flag: //程序会跳回此处重新开始运转
printf("a\n");
printf("b\n");
goto flag; //跳转
示例:关机程序,程序运转后,电脑就在1min内关机,假如输入中止,则撤销关机。
//指令行cmd关机
// shutdown -s -t 60 60s后关机
// shutdown -a 中止关机
#include <string.h>
int main()
{
//关机
//C言语供给了一个函数:system() -履行系统指令的函数
char input[20] = { 0 }; //寄存输入的信息
system("shutdown -s -t 60");
again:
printf("请留意,电脑将在1分钟内关机,假如输入「中止」,则撤销关机\n");
scanf("%s", input);
if (strcmp(input, "中止") == 0)
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
去掉goto改造版:
#include <string.h>
int main()
{
char input[20] = { 0 }; //寄存输入的信息
system("shutdown -s -t 60");
while (1)
{
printf("请留意,电脑将在1分钟内关机,假如输入「中止」,则撤销关机\n");
scanf("%s", input);
if (strcmp(input, "中止") == 0)
{
system("shutdown -a");
break;
}
}
return 0;
}
但某些场合下goto句子也能起到作用,最常见的用法便是中止程序在某些深度嵌套的结构的处理进程,例如一次跳出两层或多层循环。
此刻break的效果就不如goto,因为它只能从最内层循环推出到上一层的循环。
例如:
for(...)
{
for(...)
{
for(...)
{
if(disaster)
goto error;
}
}
}
error:
if(disaster) //处理过错状况
留意:goto
句子只能在一个函数范围内跳转,不能跨函数。
以下状况是过错的:
void test()
{
flag:
printf("test\n");
}
int main()
{
goto flag;
return 0;
}