第02章 C语言入门专题(下)

本文详细介绍了C语言中的基本语句类型,包括选择语句(if、switch)、循环语句(while、do-while、for)以及跳转语句。接着,阐述了函数的定义、调用、声明和递归使用。此外,讨论了程序的组织结构,如单文件和多文件程序,以及变量和函数的声明选项,包括存储类别、作用域和链接。最后,提到了变量的声明和初始化以及函数指定符如inline和_noreturn。
摘要由CSDN通过智能技术生成

声明:本文仅为个人学习总结,还请批判性查看,如有不同观点,欢迎交流。

摘要

本文针对C语言的数据处理部分,总结记录了:程序的基本执行单元“语句”(主要是选择语句和循环语句)、对语句进行模块化封装的“函数”、对函数进行组织的“程序结构”,以及在结构实现过程中用到的“变量和函数的声明选项”。


1、语句(statement)

语句是C程序的基本构建块,一条语句相当于一条完整的计算机指令。大部分语句以分号结尾。可以分类为:

  1. 表达式语句
  2. 选择语句(selection statement):if 语句和 switch 语句,允许程序在一组选项中选择一条执行路径;
  3. 循环语句(iteration statement):while 语句、do while 语句和 for 语句,支持循环(重复)操作;
  4. 跳转语句(jump statement):break 语句、continue 语句、goto 语句和 return 语句,无条件跳转到程序中的某个位置;
  5. 复合语句(compound statement):用花括号括起来的一条或多条语句;
  6. 空语句:只有分号,不执行任何操作。

顺序结构、选择结构和循环结构,是结构化程序设计的三种基本控制结构。

1.1 选择语句

1.1.1 if 语句

// 1. if 语句
// 逻辑表达式,只有真、假两个值,可包含关系运算符和逻辑运算符
// “语句”可以是以分号结尾的简单语句,也可以是用花括号括起来复合语句
if (逻辑表达式) 
  语句

// 为便于阅读,下文将统一采用“{语句}”的描述方式
if (逻辑表达式) {
  语句
}

// 2. 带有 else 子句的 if 语句
if (逻辑表达式) {
  语句
}
else {
  语句
}

// 3. 级联式 if 语句
// 仍然是普通的 if 语句,只是恰巧有另外一条 if 语句作为 else 子句,以此类推
// else 子句属于“离它最近的、并且还未和其它 else 匹配的” if 语句
if (逻辑表达式) {
  语句
}
else if (逻辑表达式) {
  语句
}
...
else if (逻辑表达式) {
  语句
}
else {
  语句
}

1.1.2 switch 语句

// 不允许有重复的分支标号
// 分支顺序任意,default分支不一定要放置在最后(不推荐)
// 在执行完目标分支中的最后一条语句后,如果没有 break 语句(或者其它跳转语句),控制将继续流向下一个分支(忽略其分支标号)
switch (整型表达式) {
  case 常量表达式 : 语句
  ...  
  case 常量表达式 : 语句
  default : 语句
}

1.2 循环语句

1.2.1 while 语句

// 在循环体执行之前测试控制表达式,值为真则执行
while (控制表达式) {
  语句
}

1.2.2 do while 语句

// 在循环体执行之后测试控制表达式(本质上是 while 语句)
do {
  语句
} while (控制表达式);

1.2.3 for 语句

// 适合需要递增或递减计数变量的循环
// 如果同时省略表达式1、表达式3,则等同于 while 循环
// 如果省略第二个表达式,它默认为真值,因此 for 语句不会终止(除非以其它形式退出循环)
// 可以使用逗号表达式作为第一个或第三个表达式
for (声明或者表达式1; 表达式2; 表达式3) {
  语句
}

// 与 for 循环等价的 while 循环
表达式1;
while (表达式2) {
  语句
  表达式3;
}

// 逗号表达式(comma expression)
// 逗号运算符允许将两个表达式“拼接”在一起,构成一个表达式(类似复合语句,把一组语句当作一条语句来使用)
// 表达式1和表达式2是两个任意的表达式
// 首先计算表达式1,并且扔掉其计算出的值;然后计算表达式2,把这个值作为整个表达式的值
表达式1, 表达式2

// 逗号表达式示例
i = 1, j = 2, k = i + j

1.2.4 退出循环(break、continue、goto)

  • break 语句

    • 把程序控制从包含该语句的最内层 while、do、for 循环 或 switch 语句中转移出来
    • 跳转目的地:包含该语句的循环体末尾之后
  • continue 语句(无法跳出循环)

    • 跳过某次迭代的部分内容,但是不会跳出整个循环
    • 跳转目的地:包含该语句的循环体末尾之前
  • goto 语句

    • 允许程序从一条语句跳转到另一条语句
    • 跳转目的地:函数中任何有标号的语句处(不可以绕过变长数组的声明)

1.2.5 空语句(;)

主要用于编写具有空循环体的循环。

// 示例:寻找素数的循环
for (d = 2; d < n && n % d != 0; d++) {
  ; // empty loop body
}

2、函数

2.1 函数的定义和调用

// 函数定义(function definition)
返回类型 函数名(形式参数列表)
复合语句

/**
 * @brief 计算两个 double 类型数值的平均值
 * @param[in] a 形式参数(formal parameter),类型为 double
 * @param[in] b 形式参数(形参)本质上是变量,在调用函数时提供初始值
 * @return  返回类型(return type)为 double
 */
double average(double a, double b)
{
  // 函数体(body),是带有花括号的复合语句
  double avg = (a + b) / 2;

  // return 语句的语法:return 表达式;
  // 执行 return 语句会使函数“返回”到调用它的地方
  // 如果表达式类型和函数返回类型不匹配,系统会把表达式的类型隐式地转换成返回类型
  // 对于 void 函数,不需要表达式(return;),也可以没有 return 语句
  return avg;
}

// 函数调用(function call):
// 通过“函数名及跟随其后的实际参数(actual argument)列表”进行函数调用
// 实际参数(实参)是值传递,调用函数时,将实参的值赋给相应的形参;执行函数时,对形参的改变不会影响实参的值
// 如果实参类型与形参类型不匹配,实参会被隐式地转换成相应形参的类型
// 函数值可以存储在变量中(如果存在,并且需要)
double avg = average(x, y);

2.2 函数的声明

// 函数声明(function declaration)/ 函数原型(function prototype)
// 为“如何调用函数”提供完整描述:包括实参数量、类型,以及返回值类型
// 使编译器可以先对函数进行初步了解,函数的完整定义将在以后给出
// 声明格式:函数定义的第一行 + 分号(;)结尾
返回类型 函数名(形式参数列表);

// 可以省略形参名字,只保留形参类型(不推荐)
double average(double, double);

2.3 程序终止

方式1:在 main 函数中执行 return 语句

/**
 * @brief  “主”程序函数,C程序从 main() 开始执行,它提供了程序的基本框架
 * @param[in]  void 表明没有参数(可省略)(有时会有两个参数,通常名为 argc 和 argv)
 * @return  在程序终止时,向操作系统返回状态码,0为正常终止;非0为异常终止;还可以根据需要自定义
 */
int main(void)
{
  ...
  return 0;
}

方式2:调用 exit 函数

/**
 * @file  <stdlib.h>
 * @brief  调用所有用 atexit 函数注册的函数,清洗全部输出缓冲区,关闭所有打开的流,移除任何由 tmpfile 产生的文件,并终止程序。
 * @param[in] status 值为 0 或 EXIT_SUCCESS 代表程序正常终止,EXIT_FAILURE 代表异常终止
 */
void exit(int status);

方式比较:

  • main 函数(返回值为 int 类型)中的 return 表达式; 通常等价于 exit(表达式);
  • 只有 main 函数,调用 return 语句时才会导致程序终止
  • 任意函数,调用 exit 函数都会导致程序终止

2.4 递归

如果函数调用它本身,那么此函数就是递归的(recursive)。经常用于快速排序(quicksort)等分治法(divide-and-conquer)的实现。

// 示例:利用公式 n!=n×(n-1)! 递归地计算 n!
int fact(int n)
{
  if (n <= 1) // 为了防止无限递归,所有递归函数都需要某些类型的终止条件
    return 1;
  else
    return n * fact(n - 1); // 递归调用 fact()
}

3、程序结构

编排C程序的各种构成元素。

3.1 单文件

单文件示例:

// 1. #include 指令,引入外部信息,供整个文件使用(执行到预处理指令所在的代码行时,预处理指令才会起作用)
#include <stdio.h>

// 2. #define 指令,定义宏,提供给整个文件使用
#define BUTTER_WEIGHT 56

// 3. 类型定义,供后续代码使用(类型名定义后才可以使用)
typedef int Pounds;

// 4. 外部/全局变量的声明,供后续函数使用(变量声明后才可以使用)
Pounds units = 0;

// 5. 除 main 函数之外的函数的原型,提前声明函数,可以在调用函数时,不用考虑其定义位置
void critic(int times);

// 6. main 函数的定义,在其它函数前定义 main 函数,使得阅读程序的人容易定位程序的起始点
int main(void) {
  int times = 1;

  printf("How many pounds to a firkin of butter?\n");
  scanf("%d", &units);

  while (units != BUTTER_WEIGHT) {
    critic(times++);
  }
  printf("You must have looked it up!\n");
  return 0;
}

// 7. 其它函数的定义
void critic(int times) {
  printf("You've attempted %d time(s). No luck, my friend. Try again.\n", times);
  scanf("%d", &units);
}

3.2 多文件

3.2.1 源文件(source file)

  • 扩展名为 .c
  • 每个源文件包含程序的部分内容,主要是函数和变量的定义
  • 其中的一个源文件必须包含一个名为 main 的函数(作为程序的起始点)

3.2.2 头文件(header file)

  • 扩展名为 .h
  • 包含可以在源文件之间共享的信息,例如函数原型、变量声明、宏定义、类型定义等

3.2.2.1 #include 指令

告诉预处理器打开指定的文件,并且把此文件的内容插入当前文件中。

# include <文件名> // <> 用于属于C语言自身库的头文件,搜寻系统头文件所在的目录(s)
# include "文件名" // "" 用于所有其他头文件,包括自己编写的文件,先搜寻当前目录,然后搜寻系统头文件所在的目录(s)
# include "..\include\utils.h" // 为提高可移植性,建议使用相对路径而不是绝对路径
# include 预处理记号 // 可以用宏来定义文件名,这样就不需要把文件名“硬编码”到指令里面去

3.2.2.2 共享宏定义和类型定义

#define BOOL int
#define TRUE 1
#define FALSE 0
typedef int Bool;

3.2.2.3 共享函数原型

  • 在源文件中定义函数,在头文件中声明函数;
  • 在含有函数 f 定义的源文件中,始终包含声明函数 f 的头文件,避免声明与定义不匹配;
  • 仅用于源文件内部的函数不需要在头文件中声明。

3.2.2.4 共享变量声明

// .c 源文件
// 定义式声明:声明并定义变量 i,使编译器为 i 留出存储空间,可以在定义的同时进行初始化赋值
int i;

// .h 头文件
// 引用式声明:extern 告诉编译器,变量 i 是在程序中的其他位置定义的,不需要为其分配存储空间
extern int i;

在程序中,变量可以有多次声明,但只能有一次定义。变量的 extern 声明不是定义(但对变量进行初始化的 extern 声明是变量的定义,extern int i = 0; 等效于 int i = 0;)。

3.2.2.5 保护头文件

如果源文件包含同一个头文件两次,那么可能产生编译错误(如果头文件中包含类型定义)。特别是在头文件包含其他头文件时。为了防止头文件多次包含,可以使用 #ifndef 和 #endif 指令来封闭文件的内容。

// 头文件 boolean.h 保护示例
#ifndef BOOLEAN_H
#define BOOLEAN_H
#define TRUE 1
#define FALSE 0
typedef int Bool;
#endif

4、变量/函数的声明选项

声明为编译器提供有关标识符(变量或函数)含义的信息。

4.1 声明的语法

声明形式:声明指定符 声明符;

  • 声明指定符(declaration specifier),描述变量或函数的性质,可以分为以下4类:(2、3顺序可换)
    1. 存储类型:auto、static、extern、register 和 _Thread_local(C11),在声明中最多可以出现一种存储类型(_Thread_local 和 static/extern 例外),且放在最前面;
    2. 类型限定符:const、volatile、restrict(C99)、_Atomic(C11),声明中可以包含零个或多个限定符;
    3. 类型指定符:int、long、signed 等关键字(可组合)、结构/联合/枚举的说明、用 typedef 创建的类型名等;
    4. 函数指定符:inline(C99)、_Noreturn(C11),只用于函数声明。
  • 声明符(declarator),给出变量或函数的名字,并且可以提供关于其性质的额外信息。
    • 可以只是一个标识符(简单变量的名字);
    • 可以是标识符和 []、() 以及 * 的各种组合,用来表示指针、数组或者函数;
    • 声明符之间用逗号分隔;
    • 表示变量的声明符后边可以跟随初始化器。

声明规则示例:

声明示例函数指定符① 存储类型②/③ 类型限定符③/② 类型指定符④ 声明符
static float x, y, *p;staticfloatx, y, *p;
const char month[] = “July”;constcharmonth[] = “July”;
extern const unsigned long int a[10];externconstunsigned long inta[10];
extern int square(int);externintsquare(int);
inline static int average(int a, int b);inlinestaticintaverage(int a, int b);

4.2 变量的性质

C程序中的每个变量都具有以下3个性质:

  1. 存储期:数据都存储在内存中,变量的存储期决定了变量在内存中保留的时间;
  2. 作用域:是指变量名字可以被访问的范围,也就是可以通过名字引用变量的那部分程序区域;
  3. 链接:是指变量的链接属性,确定了变量名字在不同源文件之间的可见性。

不同性质的变量具有不同的存储期、作用域和链接。

4.2.1 作用域(scope)

变量的作用域:是可以引用该变量名字的程序区域,可分为块作用域、函数作用域、函数原型作用域和文件作用域。

  1. 块作用域(block scope):变量定义/声明在块中,作用范围从定义/声明处开始一直到所在块的末尾。局部变量(local variable)

程序块(block):用一对花括号括起来的代码区域,表示函数体或者复合语句(可能包含声明)

  • 整个函数体是一个块,形式参数也具有块作用域,属于函数体这个块(形参是通过实参初始化的局部变量);
  • 函数中的任意复合语句也是一个块;
  • 选择语句(if和switch)、循环语句(while、do和for)以及它们所控制的“内部”语句(即使没有用花括号括起来)也被视为一个块。
  1. 函数作用域(function scope):仅用于 goto 语句的标签。一个标签即使出现在函数的内层块中,它的作用域也延伸至整个函数。

  2. 函数原型作用域(function prototype scope):用于函数原型中的形参名,作用范围是从形参定义处开始一直到原型声明结束。只有在变长数组中,形参名才有用:

// 方括号中必须使用在函数原型中已声明的名称
void f(int n, int m, ar[n][m]);
  1. 文件作用域(file scope):变量定义/声明在所有函数外面,作用范围从定义/声明处开始一直到所在文件的末尾。全局变量(global variable)/外部变量(external variable)
  • 作用域规则:当程序块内的声明命名一个标识符时,如果此标识符已经是可见的(因为此标识符拥有文件作用域,或者因为它已在某个程序块内声明),新的声明临时“隐藏”了旧的声明,标识符获得了新的含义。在程序块的末尾,标识符重新获得旧的含义。
  • C99和C11标准都要求编译器识别局部标识符的前63个字符和外部标识符的前31个字符。以前的标准为识别局部标识符前31个字符和外部标识符前6个字符。

4.2.2 链接(linkage)

变量的链接:是指变量名字的链接属性,确定了变量名字在不同源文件之间的可见性,也就是共享范围。(作用域是为编译器服务的,链接是为链接器服务的)

序号链接属性作用域声明关键字说明
1无链接块作用域、函数作用域、函数原型作用域/变量属于定义它们的块、函数或原型私有
2内部链接文件作用域static变量只能在一个文件内访问
3外部链接文件作用域/变量可以在多个文件间访问
int g = 5;           // 文件作用域,外部链接,可以在其它文件访问
static int s = 3;    // 文件作用域,内部链接,只能在本文件内访问
int main(void) {
  int i = 0;         // 块作用域,无链接,只能在块内访问
  ...
}

4.2.3 存储期(storage duration)

变量的存储期:是程序执行时,能够确保变量的存储空间必定存在的那一段时间。

序号存储期作用域声明关键字说明默认初始值
1静态存储期文件作用域/程序开始执行时为变量分配内存,运行过程中一直存在各字节为0
块作用域static同上(其他函数可以通过地址间接访问)各字节为0
2线程存储期_Thread_local线程私有,线程启动时创建和初始化,线程结束时将其销毁同静态存储期
3自动存储期块作用域/程序进入块时,分配内存;退出块时,释放内存(变长数组的存储期从声明处到块的末尾)
4动态分配存储期//根据需要,调用库函数分配和释放内存由库函数决定

4.2.4 存储分类(storage class)

不同的存储分类具有不同的存储期、作用域和链接,对应着不同的变量性质。

序号存储分类存储期作用域链接声明关键字分类命名
1自动自动(auto)自动/局部变量
2寄存器自动register寄存器变量
3静态外部链接静态文件外部/外部/全局变量
4静态内部链接静态文件内部static外部静态变量?
5静态无链接静态static局部静态变量
6线程外部链接线程文件外部_Thread_local
7线程内部链接线程文件内部static、_Thread_local
8线程无链接线程static、_Thread_local

4.3 存储类型

存储类型可以用于变量、函数和形式参数的说明。

4.3.1 变量存储类型

变量的声明位置决定了默认的存储期、作用域和链接。

// 默认存储类型
int g; // 在函数外部声明的变量具有静态存储期,它的名字具有文件作用域和外部链接。
int f(void) {
  int i = 0; // 在块(包括函数体)内部声明的变量具有自动存储期,它的名字具有块作用域,并且无链接。
  ...
}

当这些默认的性质无法满足要求时,可以通过指定存储类型来改变变量的性质。

序号存储类型变量的性质
1auto默认类型,仅用于块作用域的变量,为自动存储期,无链接
2register仅用于块作用域的变量,为自动存储期,无链接;适用快速频繁访问的变量,不能获取变量地址
3static用于全部变量,具有静态存储期,用于文件作用域变量时,为内部链接;用于块作用域变量时,为无链接
4extern声明已在别处定义过的变量。具有静态存储期;可具有块作用域或文件作用域;如果变量的定义式声明为 static,那么它的名字具有内部链接;否则(通常情况下),具有外部链接
5_Thread_local具有线程存储期;用于块作用域变量时,需要与 static 组合,为无链接;用于文件作用域变量时,如果与 static 组合,为内部链接;否则,为外部链接

4.3.2 函数存储类型

函数声明(和定义)也可以包括存储类型,但是选项只有 extern 和 static。

  • extern 说明函数的名字具有外部链接,允许其它文件调用此函数(默认选项,类似声明变量是 auto);
  • static 说明函数的名字具有内部链接,只能在定义函数的文件内部调用此函数。
extern int f(int i); // 具有外部链接,允许其它文件调用
static int g(int i); // 具有内部链接,不允许其它文件直接调用(可通过函数指针间接调用)
int h(int i);        // 默认情况下,具有外部链接

4.3.3 形参存储类型

函数的形式参数具有和 auto 变量相同的性质:自动存储期、块作用域和无链接。唯一能用于形式参数的存储类型是 register。

4.3.4 存储类型示例

int a;             // a,静态存储期,文件作用域,外部链接
extern int b;      // b,静态存储期,文件作用域,需要根据 b 的定义式声明确定链接属性
static int c;      // c,静态存储期,文件作用域,内部链接
static void f(int d, register int e) {
                   // d,自动存储期,块作用域,无链接
                   // e,自动存储期,块作用域,无链接
                   // f,内部链接
  auto int g;      // g,自动存储期,块作用域,无链接
  int h;           // h,自动存储期,块作用域,无链接
  static int i;    // i,静态存储期,块作用域,无链接
  extern int j;    // j,静态存储期,块作用域,需要根据 j 的定义式声明确定链接属性
  register int k;  // k,自动存储期,块作用域,无链接
}
_Thread_local int l;          // l, 线程存储期,文件作用域,外部链接
_Thread_local static int m;   // m, 线程存储期,文件作用域,内部链接
extern int n(void) {          // n, 外部链接(extern 可省略)
  _Thread_local static int o; // o, 线程存储期,块作用域,无链接
}

4.4 类型限定符

限定符用于限制变量的使用方式。

序号类型限定符说明
1const变量/对象的值在初始化以后,不能被修改。(对表示数或字符的常量建议使用 #define,可用于常量表达式)
2volatile外部代理(不是变量所在的程序)可以改变该变量的值(例如用于实时保存输入设备数据的内存空间),避免编译器进行错误的优化处理。
3restrict(C99)只能用于指针,限定该指针是访问它所指向内存的唯一方式(在特定作用域中),利于编译器进行优化处理。
4_Atomic(C11)用于除数组和函数之外的类型,当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。

4.5 声明符

声明符包含标识符(被声明的变量或函数的名字),标识符的前边可能有符号*,后边可能有[]或()。通过把*、[]和()组合在一起,可以创建复杂声明符。

简单声明符示例:

int i;          // 声明符就是标识符
int *p;         // 用*开头的声明符表示指针
int a[10];      // 用[]结尾的声明符表示数组
                // 如果数组是形式参数,或者数组有初始化器,再或者数组的存储类型为extern,那么方括号内可以为空
                // 在多维数组中,只有第一维的方括号可以为空
                // C99数组形参声明可以为 [static 最小长度],也可以通过 [*] 指示变长数组参数
int abs(int i); // 用()结尾的声明符表示函数

复杂声明符解释规则:

  • 定位声明的标识符,然后从标识符开始 从内往外解释声明符
  • 在做选择时,始终使 [] 和 () 优先于 *
    • 如果 * 在标识符的前面,而标识符后边跟着 [],那么标识符表示数组而不是指针
    • 如果 * 在标识符的前面,而标识符后边跟着 (),那么标识符表示函数而不是指针

复杂声明符示例:(组合符号*、[] 和 ())

int *ap[10];          // ap是标识符,对于前后的符号,[]优先级高于*,所以ap是数组,数组元素为指针
float *fp(float);     // fp是标识符,对于前后的符号,()优先级高于*,所以fp是函数,函数的返回类型是指针
void (*pf)(int);      // (*pf)优先级最高,所以pf是指针;(*pf)后边跟着(int),所以pf指向函数;函数为int型参数,返回void类型
int *(*x[10])(void);  // 1. 定位声明的标识符(x)
                      // 2. 在x前边有*,后边有[],[]优先级高于*,所以(x是数组,数组中元素为指针)
                      // 3. 对于(*x[10]),前边有*,后边有(),()优先级高于*,所以(指针指向函数,函数不带参数)
                      // 4. 函数的返回类型为(指向int型值的指针)

不支持的声明:

int f(int)[];     // 错误,函数不能返回数组,但可以返回指向数组的指针
int g(int)(int);  // 错误,函数不能返回函数,但可以返回指向函数的指针
int a[10](int);   // 错误,数组不能包含函数,但是数组可以包含指向函数的指针

4.6 初始化器

在声明变量时为它们指定初始值。

格式:声明符 = 初始化器

int i = 5 / 2 ;            // 简单变量的初始化器是一个与变量类型一样的表达式,如果类型不匹配,用和赋值运算相同的规则对初始化器进行类型转换
int *p = &i;               // 指针变量的初始化器必须是具有和变量相同类型或void*类型的指针表达式
int a[3] = {1, 2, 3};      // 数组、结构或联合的初始化器通常是带有花括号的一串值(C99开始可以有其他形式)
struct part part2 = part1; // 自动类型的结构或联合的初始化器可以是另外一个结构或联合
static int s = 1;          // 具有静态存储期的变量的初始化器必须是常量表达式

4.7 函数指定符

4.7.1 内联函数(inline function, C99)

“内联”表明编译器把函数的每一次调用都用函数的机器指令来代替。会增加被编译程序的大小,但可以避免函数调用的额外开销。

// 声明为 inline 并不是强制编译器将代码内联编译,只是建议编译器应该使函数调用尽可能地快
// inline 类似于 register 和 restrict 关键字,用于提升程序性能,但可以忽略
inline double average(double a, double b) {
  return (a + b) / 2;
}
  • 内联函数应该比较短小,这样相比于执行函数体的时间,节约调用函数的时间才更有意义;
  • 如果获取内联函数的地址,编译器会生成一个非内联函数;
  • 内联函数无法在调试器中显示;
  • 编译器优化内联函数必须知道该函数的定义,一般情况下内联函数具有内部链接,定义与调用在同一个文件中;
  • 如果多个文件都要使用某个内联函数,可以把内联函数定义放在头文件;
  • 对于具有外部链接的内联函数
    • 函数中不能定义可改变的 static 变量(可以同时为 static 和 const)
    • 函数中不能引用具有内部链接的变量

4.7.2 _Noreturn 函数(C11)

函数指定符 _Noreturn 表明,函数调用完成后,不会返回主调函数,也就是不会把控制交还给主调程序。

// 标准库中部分不返回的函数示例
_Noreturn void abort(void);
_Noreturn int at_quick_exit(void (* func) (void));
_Noreturn void exit(int status);
_Noreturn void _Exit(int status);
_Noreturn void quick_exit(int status);

参考

  1. [美] K. N. 金(K. N. King)著,吕秀锋,黄倩译.C语言程序设计:现代方法(第2版·修订版).人民邮电出版社.2021:209.
  2. [美] 史蒂芬·普拉达著.C Primer Plus(第6版 中文版 最新修订版).人民邮电出版社.2019:115.

宁静以致远,感谢 Vico 老师。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值