前语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中的truefalse也仅仅运用了无符号整型的10罢了。)

因而括号中的表达式能够运用各种句子,包括联系操作符等来计算。

弥补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); //输出每一个输入的字符,其实便是循环读取然后输出

弥补2getchar运用场景,需要用户输入和承认暗码:

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适用场景有限,较少被运用。

此外,breakcontinue同样能够在其中运用。

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循环将初始化、中止判别和条件调整都集合到一起,易于阅读和修改。

此外,breakcontinue也能够在for循环中运用,效果基本相同,但for中的continue会主动跳到调整部分,而while里则没有这种功能。

建议

  1. 不在for循环体内修改循环变量,避免for循环失掉操控;

  2. 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. 跳转句子

现已提到过的breakcontinuereturn便是跳转句子(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;
}