预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。C语言提供多种预处理功能,主要处理#开始的预编译指令,如宏定义(#define)、文件包含(#include)、条件编译(#ifdef)等。合理使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
重点:在ANSI C的任何一种实现中,存在两个不同的环境。
第1种是翻译环境,在这个环境中源代码被转换为可执行的机器指令。
1.组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。
2.每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。
3.链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程 序员个人的程序库,将其需要的函数也链接到程序中。
程序的执行过程:
int main() //预处理(定义)符号
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("file:%s line=%d data=%s time=%s i=%d\n", __FILE__,__LINE__ ,__DATE__,__TIME__,i);
}
return 0;
}
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__FUNCTIO__ // 文件被执行的函数
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
代码如下:
#define MAX 100
#define STR "bit education"
#define reg register
#define print printf("bit education\n")
int main() //预处理指令
{
reg int age;
printf("%d\n", MAX);
printf("%s\n", STR);
print;
return 0;
}
#define 定义宏 ,#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(definemacro)。
代码如下:
//#define SQUARE(X) X*X
#define SQUARE(X) (X)*(X)
//定义中我们使用了括号,想避免之前的问题,但是这个宏可能会出现新的错误。所以使用宏时候不要省括号
#define DOUBLE(X) (X)+(X)
int main()
{
printf("%d\n", SQUARE(3));
printf("%d\n", 3 * 3);
printf("%d\n", SQUARE(3 + 2)); //11
printf("%d\n", 3 + 2 * 3 + 2);
//printf("%d\n", SQUARE(3 + 2)); //25
printf("%d\n", 2 * DOUBLE(5));
printf("%d\n", 2 * 5 + 5);
return 0;
}
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
1. 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换。
2. 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换。
3. 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
1. 宏参数和#define定义中可以出现其他#define定义的变量。但是对于宏,不能出现递归。
2. 当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#include
#define print(format,data) printf("the value of "#data" is "format"\n",data)
//"data" == #data把参数插入到字符串中
int main()
{
float f = 4.5f;
print("%f",f);
printf("the value of f is %f\n", f);
int a = 10;
print("%d", a);
printf("the value of a is %d\n", a);
int b = 20;
print("%d", b);
printf("the value of b is %d\n", b);
return 0;
}
2.##可以把位于它两边的符号合成一个符号,它允许宏定义从分离的文本片段创建标识符,这样的连接必须产生一个合法的标识符。否则其结果就是未定义的。
代码如下:
#define CAT(X,Y) X##Y
int main()
{
int Class100 = 1000;
printf("%d\n", CAT(Class, 100));
return 0;
}
当宏参数在宏的定义中出现超过一次的时候,如果参数带有副作用,那么你在使用这个宏的时候就可能出现危险,导致不可预测的后果。副作用就是表达式求值的时候出现的永久性效果。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#define MAX(X,Y) ((X)>(Y)?(X):(Y)) //(((a++)>(b++)?(a++):(b++)))
int main()
{
int a = 10;
int b = 20;
int m = 5;
int n1 = m+1;
printf("m=%d,n1=%d\n", m, n1);
int n2 = ++m; //此时m为带有副作用,因为m的值也在变
printf("m=%d,n1=%d\n", m, n2);
int ret = MAX(a++, b++);
printf("a=%d,b=%d\n", a, b);
printf("ret=%d\n", ret);
return 0;
}
重点:宏和函数的对比
当然和宏相比函数也有劣势的地方:
代码如下:
#include
#define MAX 100
int main()
{
printf("%d\n", MAX);
#undef MAX
printf("%d\n", MAX); //此时MAX报错
return 0;
}
命令行定义:
许多C 的编译器提供了一种能力,允许在命令行中定义符号。用于启动编译过程。 例如:当我们根据同一个源文件要编译出不同的一个程序的不同版本的时候,这个特性有点用处。(假定某个程序中声明了一个某个长度的数组,如果机器内存有限,我们需要一个很小的数组,但是另外一个机器内存大写,我们需要一个数组能够大写。)
代码如下:
gcc - D sz = 10
int main()
{
int arr[sz] = {
0 };
int i = 0;
for (i = 0; i<sz; i++)
{
arr[i] = i;
}
for (i = 0; i<sz; i++)
{
printf("%d\n", arr[i]);
}
return 0;
}
条件编译:满足某种条件时就参与编译,不满足条件时就不参与编译
代码如下:
#define __DEBUG__ 1
#define PRINT 1
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", i);
#ifdef __DEBUG__
printf("bit education\n");
#endif
}
#ifdef PRINT //正面
printf("哈哈\n");
#endif
#ifndef PRINT //反面
printf("哈哈啊\n");
#endif
#if defined PRINT //正面
printf("bit\n");
#endif
#if !defined PRINT //反面
printf("bit啊\n");
#endif
#if 5-3
printf("bit NB\n");
#elif 5-5
printf("bit CA\n");
#elif
printf("呵呵\n");
#endif
return 0;
}
常见的条件编译指令:
代码如下:
常见的条件编译指令:
1.
#if 常量表达式
...
#endif //常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
..
#endif
2.多个分支的条件编译
#if 常量表达式
...
#elif 常量表达式
...
#else
...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
offsetof该宏用于求结构体中一个成员在该结构体中的偏移量。
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include
#include"标头.h" //头文件的包含->相当于进行了函数的声明
#include
#define MY_OFFSET0F(struct_name,mem_name) (int)&(((struct_name*)0)->mem_name)
struct stu
{
char c;
int i;
double d;
};
int main()
{
int a = 10;
int b = 20;
int sum = add(a, b);
printf("%d\n", sum);
printf("%d\n", offsetof(struct stu, c));
printf("%d\n", offsetof(struct stu, i));
printf("%d\n", offsetof(struct stu, d));
printf("%d\n", MY_OFFSET0F(struct stu, c));
printf("%d\n", &(((struct stu*)0)->c));
printf("%d\n", MY_OFFSET0F(struct stu, i));
printf("%d\n", MY_OFFSET0F(struct stu, d));
return 0;
}
#include
#include"标头.h" 应用于自定义的头文件,如果使用"",查找去当前工程的目录下查找,如果找不到,再去库目录下查找。
当前目录 : D : \visua stdio 2013代码\预处理符号、预处理指令详解6文件包含\预处理符号、预处理指令详解6文件包含。
如果程序中的函数要使用同一个公共模块用条件编译来解决每个头文件的开头写:
#ifndef TEST_H
#define TEST_H
头文件的内容
#endif //TEST_H
或者:
#pragma once
就可以避免头文件的重复引入。
以上就是今天要讲的内容,本文简单介绍了C语言中程序环境和预处理的相关问题,如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。