C语言归纳六-预处理

目录

一、预处理命令简介

二、#include用法介绍

三、#define用法介绍

3.1 定义

3.2 作用域

3.3 不生效情况

3.4 简单特点

3.5 #define和#typedef区别:

3.6 C语言带参数的宏定义(参数外面最好套括号)

3.7 C语言宏参数的字符串化-#的用法

3.8 C语言宏参数的连接-##的用法

3.9 C语言中几个预定义宏、

VS下的输出结果:Date : Mar  6 2016Time : 11:47:15File : main.cLine : 8四、条件编译预处理命令

五、C语言#error命令(阻止程序编译)

六、C++预处理命令总结:


一、预处理命令简介

使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。

编译是针对单个源文件的,一次编译操作只能编译一个源文件。如果程序中有多个源文件,就需要多次编译操作。

链接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。

在实际开发中,有时候在编译之前还需要对源文件进行简单的处理。例如,我们希望自己的程序在 Windows 和 Linux 下都能够运行,那么就要在 Windows 下使用 VS 编译一遍,然后在 Linux 下使用 GCC 编译一遍。但是现在有个问题,程序中要实现的某个功能在 VS 和 GCC 下使用的函数不同(假设 VS 下使用 a(),GCC 下使用 b()),VS 下的函数在 GCC 下不能编译通过,GCC 下的函数在 VS 下也不能编译通过,怎么办呢?

这就需要在编译之前先对源文件进行处理:如果检测到是 VS,就保留 a() 删除 b();如果检测到是 GCC,就保留 b() 删除 a()。

这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。

预处理主要是处理以#开头的命令,例如#include 等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。

预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。

编译器会将预处理的结果保存到和源文件同名的.i文件中,例如 main.c 的预处理结果在 main.i 中。和.c一样,.i也是文本文件,可以用编辑器打开直接查看内容。

C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。

举例:

假如现在要开发一个C语言程序,让它暂停 5 秒以后再输出内容,并且要求跨平台,在 Windows 和 Linux 下都能运行,怎么办呢?

这个程序的难点在于,不同平台下的暂停函数和头文件都不一样:

  • Windows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds)(注意 S 是大写的),参数的单位是“毫秒”,位于  头文件。
  • Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds),参数的单位是“秒”,位于  头文件。


不同的平台下必须调用不同的函数,并引入不同的头文件,否则就会导致编译错误,因为 Windows 平台下没有 sleep() 函数,也没有 头文件,反之亦然。这就要求我们在编译之前,也就是预处理阶段来解决这个问题。请看下面的代码:

#include 
//不同的平台下引入不同的头文件
#if _WIN32  //识别windows平台
#include 
#elif __linux__  //识别linux平台
#include 
#endif
int main() {
    //不同的平台下调用不同的函数
    #if _WIN32  //识别windows平台
    Sleep(5000);
    #elif __linux__  //识别linux平台
    sleep(5);
    #endif
    puts("http://c.biancheng.net/");
    return 0;
}

你看,在不同的平台下,编译之前(预处理之后)的源代码都是不一样的。这就是预处理阶段的工作,它把代码当成普通文本,根据设定的条件进行一些简单的文本替换,将替换以后的结果再交给编译器处理。

二、#include用法介绍

 

使用尖括号< >和双引号" "的区别在于头文件的搜索路径不同:

  • 使用尖括号< >,编译器会到系统路径下查找头文件;
  • 而使用双引号" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。

所以系统头文件一般用尖括号,自定义头文件一般用双引号。

「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。

三、#define用法介绍

3.1 定义

#define 叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。#define不加引号。

3.2 作用域

宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束(问题:在另一个源文件中也有用吗,需要测试一下,如果暗中全局变量的理解来看是有用的,但是我觉得会有变化)。如要终止其作用域可使用#undef命令。例如:

#define PI 3.14159

int main(){
    // Code
    return 0;
}

#undef PI

void func(){
    // Code
}

表示 PI 只在 main() 函数中有效,在 func() 中无效。

3.3 不生效情况

代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替,例如:

#include 
#define OK 100
int main(){
    printf("OK\n");
    return 0;
}

3.4 简单特点

宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。

习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。

可用宏定义表示数据类型,但是尽量不要表示指针类型,因为*号结合标识符而不是数据类型。例如:

#define UINT unsigned int

3.5 #define和#typedef区别:

宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

#define PIN1 int *
typedef int *PIN2;  //也可以写作typedef int (*PIN2);

PIN1 a, b;
在宏代换后变成:
int * a, b;
表示 a 是指向整型的指针变量,而 b 是整型变量。

然而:
PIN2 a,b;
表示 a、b 都是指向整型的指针变量。因为 PIN2 是一个新的、完整的数据类型。
由这个例子可见,宏定义虽然也可表示数据类型, 但毕竟只是简单的字符串替换。
在使用时要格外小心,以避出错。

尽量不要用宏来简化指针数据类型,麻烦。

3.6 C语言带参数的宏定义(参数外面最好套括号)

#include 
#define MAX(a,b) (a>b) ? a : b
无需注明a,b的数据类型,因为不给a,b分配内存,纯粹代码替换而已,预处理就替换过宏了,不参加编译。
函数则不同,形参和实参是两个不同的变量,都有自己的作用域。形参是要求有数据类型的。
int main(){
    int x , y, max;
    printf("input two numbers: ");
    scanf("%d %d", &x, &y);
    max = MAX(x, y);
    printf("max=%d\n", max);
    return 0;
}
比较下面两个代码:
(1)
#include 
#define SQ(y) (y)*(y)
int main(){
    int a, sq;
    printf("input a number: ");
    scanf("%d", &a);
    sq = SQ(a+1);
    printf("sq=%d\n", sq);
    return 0;
}
(2)
#include 
#define SQ(y) y*y
int main(){
    int a, sq;
    printf("input a number: ");
    scanf("%d", &a);
    sq = SQ(a+1);
    printf("sq=%d\n", sq);
    return 0;
}

3.7 C语言宏参数的字符串化-#的用法

#用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号。例如有如下宏定义:

#define STR(s) #s
那么:
printf("%s", STR(c.biancheng.net));
printf("%s", STR("c.biancheng.net"));
分别被展开为:
printf("%s", "c.biancheng.net");
printf("%s", "\"c.biancheng.net\"");
可以发现,即使给宏参数“传递”的数据中包含引号,使用#仍然会在两头添加新的引号,而原来的引号会被转义。
#include 
#define STR(s) #s
int main() {
    printf("%s\n", STR(c.biancheng.net));
    printf("%s\n", STR("c.biancheng.net"));
    return 0;
}
运行结果:
c.biancheng.net
"c.biancheng.net"

3.8 C语言宏参数的连接-##的用法

##称为连接符,用来将宏参数或其他的串连接起来。例如有如下的宏定义:
#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00
那么:
printf("%f\n", CON1(8.5, 2));
printf("%d\n", CON2(12, 34));
将被展开为:
printf("%f\n", 8.5e2);
printf("%d\n", 123400);

3.9 C语言中几个预定义宏、

ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:

  • __LINE__:表示当前源代码的行号;
  • __FILE__:表示当前源文件的名称;
  • __DATE__:表示当前的编译日期;
  • __TIME__:表示当前的编译时间;
  • __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
  • __cplusplus:当编写C++程序时该标识符被定义。
#include 
#include 
int main() {
    printf("Date : %s\n", __DATE__);
    printf("Time : %s\n", __TIME__);
    printf("File : %s\n", __FILE__);
    printf("Line : %d\n", __LINE__);
    system("pause");
    return 0;
}

VS下的输出结果:
Date : Mar  6 2016
Time : 11:47:15
File : main.c
Line : 8
四、条件编译预处理命令

(1)#if的用法

#include 
int main(){
    #if _WIN32
        system("color 0c");
        printf("http://c.biancheng.net\n");
    #elif __linux__
        printf("\033[22;31mhttp://c.biancheng.net\n\033[22;30m");
    #else
        printf("http://c.biancheng.net\n");
    #endif
    return 0;
}

(2)#ifdef用法

VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。

没有#elif。

为了能够清楚地看到当前程序的编译模式,我们不妨在程序中增加提示,请看下面的代码:

#include 
#include 
int main(){
    #ifdef _DEBUG
        printf("正在使用 Debug 模式编译程序...\n");
    #else
        printf("正在使用 Release 模式编译程序...\n");
    #endif
    system("pause");
    return 0;
}

(3)#ifndef 的用法

#ifndef 宏名
    程序段1 
#else 
    程序段2 
#endif

如果当前的宏未被定义即是假的,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。

注意:三者之间的区别

#if 后面跟的是“整型常量表达式”,而 #ifdef 和 #ifndef 后面跟的只能是一个宏名,不能是其他的。

例如,下面的形式只能用于 #if:

#include 
#define NUM 10
int main(){
    #if NUM == 10 || NUM == 20
        printf("NUM: %d\n", NUM);
    #else
        printf("NUM Error\n");
    #endif
    return 0;
}

五、C语言#error命令(阻止程序编译)

#error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:

#error error_message

例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样做:

#ifdef WIN32
#error This programme cannot compile at Windows Platform
#endif

这将导致程序编译失败。

再如,当我们希望以 C++ 的方式来编译程序时,可以这样做:

#ifndef __cplusplus
#error 当前程序必须以C++方式编译
#endif

六、C++预处理命令总结:

指令 说明
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif 结束一个#if……#else条件编译块

 

 

 

你可能感兴趣的:(C++复习)