C语言学习笔记:控制流

53 sec read

分支结构

a ? b : c

三元表达式是最简单的分支结构,具体逻辑为如果a为true,则执行b,否则执行c。

if…else…

当使用if和else,主要需要注意的是“悬空”else的问题。C语言中默认else与离它最近的if绑定。

switch…case…

该分支结构中,传入的条件只能是int型变量(包含char、enum这些int型的拓展)。每个分支需要以break结束,否则分支会依次执行下去不会停止。

循环控制

while (表达式) 语句

当表达式为真时,执行语句,知道表达式不成立。

do 语句 while (表达式)

无论表达式是否为真,语句至少执行一遍。

for(表达式1; 表达式2; 表达式3)

  • 表达式1:循环开始前的初始化步骤
  • 表达式2:用来控制循环的终止(只要表达式为真,循环持续进行)
  • 表达式3:每次循环中最后被执行的操作

for表达式等价于如下while循环:

备注:C99后,变量可以直接到表达式1中声明。

退出循环

break

作用:

  • 跳出switch语句
  • 跳出do、while、for循环

要点:只能跳出一层嵌套

continue

作用:跳过循环体剩余部分(跳转到该次循环末尾)

要点:只能应用与循环语句

goto

goto可以将程序跳转到任何标号的语句处。如果使用太多goto语句,程序代码会变得相当不宜阅读,所以只有在非常有必要时才应该使用goto语句,比如从很深的嵌套循环中跳出。

使用示例:

输出内容:

相关内容:setjmp()、longjmp()。见下文。

错误处理

assert.h

assert通常被翻译为断言,有时也被称为诊断,主要功能是声明某种东西为真,如果为假则向标准错误打印一条诊断信息并终止程序,如果为真则不做任何操作,程序继续执行。函数原型如下:

使用示例:

以上为一个简单的除法程序,我们在这里断言分母不为0,如果为0,则会输出报错信息(如果不使用断言,当分母为0时,c语言默认返回无穷大inf)。具体如下:

assert有一个缺点:因为它引入了额外的检查,因此会增加程序的运行时间,偶尔使用一次assert可能对程序的运行速度没有很大的影响,所以通常在测试的使用使用assert,正式的时候不使用。通常禁用assert有两种方式,一种是在assert.h文件引入前,添加#define NDEBUG,另外一种方式是在编译时加上-DNDEBUG,当NDEBUG被定义后,预处理器将丢弃所有的断言,这样就消除了对性能的影响,而不用将所有断言从源文件移除。

errno.h

标准库中的一些函数通过向errno中声明的int类型errno变量存储一个错误码来表示有错误发生。我们可以在调用库函数之后检查errno是否为0来判断函数在调用过程中是否发生错误。

需要注意,在使用errno前需要手动置0,上一次错误产生后该值并不会清0。以上代码执行后,输出的内容为33,33到底是什么意思内。在我的电脑中,errno定义的内容在

  • /usr/include/asm-generic/errno-base.h
  • /usr/include/asm-generic/errno.h

具体内容类似:

可以看到这里的错误是参数超过函数的定义范围。像这样每次发生错误需要打开定义文件查看具体什么错误还是比较麻烦的,C语言中还提供了一个strerror函数,可以将具体的错误内容输出出来。需要注意的是strerror并不是存在与errno.h中,而是存在于string.h中,具体使用方法如下:

输出内容为:

除了strerror外,还有另外一个函数也可以输出错误信息,那就是perror。perror定义在stdio.h中,具体使用方式如下:

输出内容为:

可以看到这里输出的内容为传入“字符串: 错误信息”。备注以上内容会输出到stderr。

signal.h

signal.h提供了处理异常情况的工具。C语言中将异常情况称为信号。信号有2种类型:运行时错误和发生程序以外的错误(例如用户中断目前正在运行的程序)。当有错误或外部事件发生时,我们称为一个信号。大多数信号是异步的:它们可以在程序执行的任意时刻发生,而不仅仅是在程序员所知道的特定时刻。由于信号可能会在任意想不到的地方发生,因此需要用独特的方式处理他们。

signal.h定义了一系列的宏,比较常见的。下表列出了一些常见信号:

信号名称 数字表示 说明
SIGHUP 1 终端挂起或控制进程终止。当用户退出Shell时,由该进程启动的所有进程都会收到这个信号,默认动作为终止进程。
SIGINT 2 键盘中断。当用户按下<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
SIGQUIT 3 键盘退出键被按下。当用户按下<Ctrl+D>或<Ctrl+\>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为退出程序。
SIGFPE 8 发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
SIGKILL 9 无条件终止进程。进程接收到该信号会立即终止,不进行清理和暂存工作。该信号不能被忽略、处理和阻塞,它向系统管理员提供了可以杀死任何进程的方法。
SIGALRM 14 定时器超时,默认动作为终止进程。
SIGTERM 15 程序结束信号,可以由 kill 命令产生。与SIGKILL不同的是,SIGTERM 信号可以被阻塞和终止,以便程序在退出前可以保存工作或清理临时文件等。

以下为我电脑中定义的一些内容:

signal提供了两个函数raise和signal。

  • raise:动触发信号。
  • signal:监测并处理特定信号。

raise函数使用起来非常的简单,只需将信号编码传递给他即可。signal函数共有两个参数,第一个为特定的信号编码,第二个为处理这个信号的函数的指针,其中处理函数必须有一个int型的参数(信号编码会传入),并返回为void。

sig_atomic_t,这是 int 类型,在信号处理程序中作为变量使用。它是一个对象的整数类型,该对象可以作为一个原子实体访问,即使存在异步信号时,该对象可以作为一个原子实体访问。除非信号是由调用abort函数或raise函数引发的,否则新还处理函数不应该调用库函数或具有静态存储期限的变量。注意:如果信号是由信号处理函数引发的,则有可能引发无限递归,所以处理函数尽可能的简单。另外signal.h中也预定义了信号处理函数:

  • SIG_DFL:按默认方式处理信号(大多数情况会导致程序终止)
  • SIG_IGN:忽略该信号
  • SIG_ERR:看起来像信号处理函数,实际上是用来在安装处理函数时检测是否发生错误的。如果一个signal调用失败(即不能对所指定的信号安装处理函数),就会返回SIG_ERR并在errno中传入一个值。使用场景如下:

除了raise函数外,有多种方式向程序发送信号,例如按下<Ctrl+C>组合键会发送SIGINT信号,终止当前进程。还可以通过 kill 命令发送信号,语法为:kill -signal pid

setjmp.h

goto语句只允许局部性跳;也就是“在自己的函数内跳转”。C语言也提供了非局部跳转的库setjmp.h。它常用于深层嵌套的函数调用链。如果某个低层的函数中检测到一个错误,你可以立即返回到顶层函数,不必向调用链中的每个中间层函数返回一个错误标志。

在setjmp.h中最重要的内容就是setjmp宏和longjmp函数。setjmp宏标记程序中的一个位置,随后可以通过longjmp函数跳转到该位置。如果要为将来的跳转标记一个位置,可以调用setjmp宏,调用的参数是一个jmp_buf类型的变量。setjmp会在第一次调用时返回0。代码实例:

输出内容为:

setjmp宏的最初调用返回0,因此main函数会调用f1,接着,f1调用f2,f2使用longjmp函数将控制全重新转给main函数,而不是返回到f1。当longjmp函数被执行时,控制权重回到setjmp宏调用。这次setjmp宏返回1(就是longjmp调用时所指定的值)。

打赏作者
微信支付标点符 wechat qrcode
支付宝标点符 alipay qrcode

使用tqdm显示Python代码执行进度

在使用Python执行一些比较耗时的操作时,为了方便观察进度,通常使用进度条的方式来可视化呈现。Python中
标点符
34 sec read

利用SWIG实现Python调用C/C++

SWIG简介 SWIG是Simplified Wrapper and Interface Generator的
标点符
1 min read

WordPress又被黑了,解决方案记录

过了一个周末,今天整个网站打开无样式,后台无法打开,直接跳转到其他网站,才意识到网站可能被黑了。查看源代码:
标点符
1 min read

发表评论

电子邮件地址不会被公开。 必填项已用*标注