以#号开头的命令称为预处理命令。
编译是针对单个源文件的,一次编译操作只能编译一个源文件,如果程序中有多个源文件,就需要多次编译操作。
链接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。
这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)。
预处理主要是处理以#开头的命令,例如#include <stdio.h>
等。预处理命令要放在所有函数之外,而且一般都放在源文件的前面。
预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
编译器会将预处理的结果保存到和源文件同名的.i
文件中,例如 main.c
的预处理结果在 main.i
中。和.c
一样,.i
也是文本文件,可以用编辑器打开直接查看内容。
C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
栗子
// 栗子1
#include<stdio.h>
//不同平台,引入不同的头文件
#if _WIN32
#include<windows.h>
#elif _linux_
#include<unistd.h>
#endif
int main()
{
//不同平台下调用不同的函数
#if _WIN32
Sleep(5000);
#elif _linux_
sleep(5);
#endif
puts("hello world!\n");
return 0;
}
#include
叫做文件包含命令,用来引入对应的头文件(.h
文件)。
使用尖括号< >
和双引号" "
的区别在于头文件的搜索路径不同:
- 使用尖括号
< >
,编译器会到系统路径下查找头文件; - 而使用双引号
" "
,编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
也就是说,使用双引号比使用尖括号多了一个查找路径
「在头文件中定义定义函数和全局变量」这种认知是原则性的错误!不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会引起重复定义错误。
宏定义
#define
叫做宏定义命令,它也是C语言预处理命令的一种。所谓宏定义,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。
//宏定义的一般形式为:
#define 宏名 字符串
//#表示这是一条预处理命令,所有的预处理命令都以 # 开头。宏名是标识符的一种,命名规则和变量相同。
//字符串可以是数字、表达式、if 语句、函数等。
//这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串等同,它不需要双引号。
//程序中反复使用的表达式就可以使用宏定义,例如:
#define M (n*n+3*n)
需要注意的是,在宏定义中表达式(nn+3n)两边的括号不能少,否则在宏展开以后可能会产生歧义。
对 #define 用法的几点说明
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。
- 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。
- 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
- 可用宏定义表示数据类型,使书写方便
应注意用宏定义表示数据类型和用 typedef 定义数据说明符的区别。宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。
带参数的宏定义
//一般形式
#define 宏名(参数列表) 字符串
//带参调用的一般形式
宏名(实参列表);
栗子
#include<stdio.h>
#define MAX(a,b) ((a>b)?a:b)
int main()
{
int x,y,max;
printf("input two numbers:\n");
scanf("%d %d",&x,&y);
max=MAX(x,y);
printf("max=%d\n",max);
return 0;
}
对带参宏定义的说明
- 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。
- 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错
- 对于带参宏定义不仅要在参数两侧加括号,还应该在整个字符串外加括号。
#的用法
#
用来将宏参数转换为字符串,也就是在宏参数的开头和末尾添加引号
#include <stdio.h>
#define STR(s) #s
int main() {
printf("%s\n", STR(hello world));
printf("%s\n", STR("hello world"));
return 0;
}
##的用法
##
称为连接符,用来将宏参数或其他的串连接起来
C语言中几个预定义的宏
ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:
- LINE:表示当前源代码的行号
- FILE:表示当前源文件的名称
- DATA:表示当前的编译日期
- TIME:表示当前的编译时间
- STDC:当要求程序严格遵循ANSI C标准时该标识被赋值为1
- __cplusplus:当编写C++程序时该标识符被定义。
栗子
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Line : %d\n", __LINE__);
printf("Date : %s\n", __DATE__);
printf("Time : %s\n", __TIME__);
printf("File : %s\n", __FILE__);
return 0;
}
C语言#if、##ifdef、#ifndef的用法详解,C语言条件编译详解
Windows 有专有的宏_WIN32
,Linux 有专有的宏__linux__
栗子
#include <stdio.h>
int main(){
#if _WIN32
system("color 0c");
printf("hello world\n");
#elif __linux__
printf("\033[22;31mhello world\n\033[22;30m");
#else
printf("hello world\n");
#endif
return 0;
}
#if用法的一般格式
#if 整形常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#elif 整型常量表达式3
程序段3
#else
程序段4
#endi
#if 命令要求判断条件为“整型常量表达式”,也就是说,表达式中不能包含变量,而且结果必须是整数;而 if 后面的表达式没有限制,只要符合语法就行。这是 #if 和 if 的一个重要区别。
ifdef的用法
#ifdef 宏名
程序段1
#else
程序段2
#endif
它的意思是,如果当前的宏已被定义过,则对“程序段1”进行编译,否则对“程序段2”进行编译。
VS/VC 有两种编译模式,Debug 和 Release。在学习过程中,我们通常使用 Debug 模式,这样便于程序的调试;而最终发布的程序,要使用 Release 模式,这样编译器会进行很多优化,提高程序运行效率,删除冗余信息。
栗子
#include <stdio.h>
#include <stdlib.h>
int main(){
#ifdef _DEBUG
printf("正在使用 Debug 模式编译程序...\n");
#else
printf("正在使用 Release 模式编译程序...\n");
#endif
system("pause");
return 0;
}
ifndef的用法
#ifndef 宏名
程序段1
#else
程序段2
#endif
与 #ifdef 相比,仅仅是将 #ifdef 改为了 #ifndef。它的意思是,如果当前的宏未被定义,则对“程序段1”进行编译,否则对“程序段2”进行编译,这与 #ifdef 的功能正好相反。
#error用法
//#error 指令用于在编译期间产生错误信息,并阻止程序的编译,其形式如下:
#error error_message
//例如,我们的程序针对 Linux 编写,不保证兼容 Windows,那么可以这样做:
#ifdef WIN32
#error This programme cannot compile at Windows Platform
#endif
//再如,当我们希望以 C++ 的方式来编译程序时,可以这样做:
#ifndef __cplusplus
#error 当前程序必须以C++方式编译
#endif