• C语言学习记录(十五)C预处理器和C库


    一、C预处理器

    C预处理器在程序执行之前查看程序(故称为预处理器)。根据程序中的预处理指令,预处理把符号缩写替换成其他的内容。

    1.1 翻译程序

    在预处理之前,编译器必须对该程序进行一些翻译处理。首先,编译器把源代码中出现的字符映射到源字符集。该处理过程处理多字节和三字符序列。

    第二,编译器定位每个反斜杠后面跟着换行符的实例,并删除它们。

    第三,编译器把文本划分成预处理记号序列、空白序列和注释序列(记号是由空格、制表符或换行符分隔的项)。编译器将用一个空格字符替换每一条注释。

    1.2 明示常量:#define

    #define预处理指令和其他预处理指令一样,以#号作为一行的开始。ANSI和后来的标准都允许#号前面有空格或制表符,而且还允许在#和指令的其余部分之间有空格。但是旧版本的C要求指令从一行的最左边开始,而且#和指令其余部分不能有空格。指令可以出现在源文件的任何地方。其定义从指令出现的地方到该文件的末尾有效。我们大量使用#define指令来定义明示常量(也叫符号常量)。但是该指令还有其他用途。下面举例说明。

    #include 
    
    #define TWO 2   /* 可以使用注释 */
    #define OW "Consistency is the last refuge of the unimagina\
    tive. - Oscar Wilde" // 反斜杠把该定义延续到下一行
    
    #define FOUR TWO*TWO
    #define PX printf("X is %d.\n", x)
    #define FMT "X is %d.\n"
    
    int main() {
    
        int x = TWO;
    
        PX;
        x = FOUR;
        printf(FMT, x);
        printf("%s\n", OW);
        printf("TWO: OW\n");
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    每行的#define(逻辑行)都有3部分组成。

    1、第一部分是#define指令本身。
    2、第二部分是选定的缩写,也称为。有些宏代表值(本例),这些宏被称为类对象宏。C语言还有类函数宏宏的名称中不允许有空格,而且必须遵循C变量的命名规则。
    3、第三部分称为替换列表或替换体。一旦预处理器在程序中找到宏的实例后,就会用替换体代替该宏。从宏变成最终替换文本的过程称为宏展开。注意,可以在#define行使用标准C注释,每条注释都会被一个空格代替。

    在这里插入图片描述

    宏可以表示明示常量,也可以表示任何字符串,甚至可以表示整个C表达式。但是要注意,虽然PX是一个字符串常量,它只打印一个名为x的变量。

    由于编译器在编译期对所有的常量表达式(只包含常量的表达式)求值,所以预处理不会进行实际的乘法运算,这一过程在编译时进行。预处理不做计算,不对表达式求值,它只进行替换。

    1.3 在#define中使用参数

    在#define中使用参数可以创建 外形和作用与函数类似的类函数宏。带有参数的宏看上去很像函数,因为这样的宏也使用圆括号。类函数宏定义的圆括号中可以有一个或多个参数,随后这些参数出现在替换体中。

    1.4 文件包含:#include

    当预处理器发现#include指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的#include指令。这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置。

    #include指令有两种形式:

    #include // 文件名在尖括号中
    #include "hot.h"  // 文件名在双引号中
    
    • 1
    • 2

    尖括号告诉预处理器在标准系统目录中查找文件。双引号告诉编译器首先在当前目录中(或文件名指定的目录)查找文件,如果未找到再查找标准系统目录。

    C语言习惯使用.h后缀表示头文件,这些文件包含需要放在程序顶部的信息。头文件经常包含一些预处理指令。有些头文件(如stdio.h)由系统提供,当然你也可以创建自己的头文件。

    1.5 其他指令

    程序员可能要为不同的工作环境准备C程序和C库包。不同的环境可能使用不同的代码类型。预处理器提供一些指令,程序员通过修改#define的值即可生成可移植的代码。#undef指令取消之前的#define定义。#if、#ifndef、#else、#elif和#endif指令用于指定什么情况下编写那些代码。#line指令用于重置行和文件信息,#error指令用于给出错误信息,#pragma指令用于向编译器发出指令。

    1.5.1 #undef指令

    #undef指令用于“取消”已定义的#define指令。

    #define ITEM 400
    
    #undef ITEM
    
    • 1
    • 2
    • 3

    将移除上面的定义。现在就可以把ITEM重新定义为一个新值。即使原来没有定义ITEM。取消ITEM的定义仍然有效。

    1.5.2 从C预处理角度看已定义

    处理器在识别标识符时,遵循与C变量命名相同的规则。当预处理器在预处理器中发现一个标识符时,它会把该标识符当作已定义或未定义的。这里的已定义表示由预处理器定义。如果标识符是同一个文件中由前面的#define指令创建的宏名,而且没有用#undef指令关闭,那么该标识符是已定义的。如果标识符不是宏,假设是一个文件作用域的C变量,那么该标识符对预处理器而言就是未定义的。

    已定义的宏可以是对象宏,包括空宏或类函数宏:

    #define LIMIT 1000 //LIMIT是已定义的
    #define GOOD	// GOOD是已定义的
    #define A(X) ((-(X))*(X)) //A是已定义的
    int q;	// q不是宏,因此是未定义的
    #undef GOOD	// GOOD取消定义,是未定义的 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意,#define宏的作用域从它在文件中的声明位置开始,直到#undef指令取消宏为止,或延伸至文件尾(以二者中先满足的条件作为宏作用域的结束)。另外还要注意,如果宏通过头文件引入,那么#define在文件中的位置取决于#include指令的位置。

    1.5.3 条件编译

    可以使用其他指令创建条件编译。也就是说,可以使用这些指令告诉编译器根据时的条件执行或忽略信息块

    1.5.3.1 #ifdef、#else和#endif指令

    #ifdef指令说明,如果预处理器已定义了后面的标识符,则执行#else或#endif指令之前的所有指令并编译所有C代码(先出现哪个指令就执行到哪里)。如果预处理器未定义,且有#else指令。则执行#else和#endif之间的所有代码。

    #ifdef TITLE // 如果已经用#define定义了TITLE,则执行下面的代码
    	#include "time.h"
    	#define START 3
    #else	// 如果没有用#define定义TITLE,则执行下面的指令
    	#include "cow.h"
    	#define START 5
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1.5.3.2 #ifndef

    #ifndef指令与#ifdef指令的用法类似,也可以和#else、#endif一起使用,但是它们的逻辑相反。#ifndef指令判断后面的标识符是否是未定义的,常用于定义之前未定义的常量。

    #ifndef SIZE
    	#define SIZE 30
    #endif
    
    • 1
    • 2
    • 3
    #ifndef _STDIO_H
    #define _STDIO-H
    
    
    # endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1.5.3.3 #if和elif指令

    if指令很像C语言中的if。#if后面跟整型常量表达式,如果表达式未非零,则表达式为真,可以在指令中使用C的关系运算符和逻辑运算符。

    #if SYS == 1
    #include "TIME.h"
    #endif
    
    • 1
    • 2
    • 3
    #if defind (IBMPC)
    	#include "TIME.h"
    #elif defind (VAC)
    	#include "VAX.h"
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、C库

    2.1 数学库

    数学库中包含许多有用的数学函数,math.h头文件提供这些函数的原型。在math.h中的函数,注意,函数中涉及的角度都以弧度为单位(1弧度 = 180/Π = 57.296度)

    在这里插入图片描述

    2.2 断言库

    assert.h头文件支持的断言库是一个用于辅助调试程序的小型库。它由assert()宏组成,接收一个整型表达式作为参数,如果表达式的值为假,assert()宏就在标准错误流(stderr)中写入一条错误信息,并调用abort()函数终止程序(abort()函数的原型在stdlib.h头文件中)。assert()宏是为了标识出程序中某些条件为真的关键位置,如果其中一个具体条件为假,就用assert()语句终止程序。通常assert()的参数是一个条件表达式或逻辑表达式。如果assert()中止了程序,它首先会显示失败的测试,包含测试的文件名和行号。

    #include 
    #include "stdlib.h"
    #include "math.h"
    #include "assert.h"
    
    int main() {
    
        int x, y, z;
        puts("please enter a number:\n");
        scanf("%d %d", &x, &y);
    
        if(y == 0){
    
            assert(y == 0);
            puts("error");
        }
        
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
  • 相关阅读:
    论文阅读---DeLF: Designing Learning Environments with Foundation Models
    OpenMARI 开源指标体系和效能提升指南之GQM从入门到精通
    初阶C++__STL__string类(使用方法+模拟实现+测试+思路分析)
    学习《Java核心技术》——第6章:接口、lambda表达式与内部类
    一个美观且功能丰富的 .NET 控制台应用程序开源库
    gazebo中添加动态障碍物
    AI算力反碎片化:世界上最快的统一矩阵乘法
    P3709 大爷的字符串题(莫队+离散)
    港陆证券:服装家纺公司上半年投资并购力度加大
    数据结构-----排序的概念、常见排序的实现以及排序算法的特点、非比较排序、排序相关例题
  • 原文地址:https://blog.csdn.net/qq_46292926/article/details/127988262