前言:
目录
首先程序环境中有两种不同的环境:
翻译环境:在这个环境中源代码被转换为可执行的机器指令
执行环境:用于实际执行代码
一、程序的翻译环境
翻译环境又分为 编译 和 链接
组成一个程序的每个源文件通过编译过程分别转换成目标代码(object )。
每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序(二进制程序)。
链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程序员个人
的程序库,将其需要的函数也链接到程序中
二、运行环境
C语言的运行环境指的是能够运行C语言程序的必要条件,包括操作系统、编译器、链接器、库等
三、预处理
预处理是翻译环境中编译上的预处理(也叫做预编译),预处理上回进行一些文本操作:注释的删除(替换为空格)、#include 头文件的包含 、#define符号的替换。接下来就用代码来说明一下
1.预处理上的文本操作
Ⅰ注释的删除
接着我们来继续打开test.i的文件
Ⅱ头文件的包含
Ⅲ#define的替换
2.预定义符号
注意:预定义符号都是语言内置的
__FILE__ 进行编译的源文件
__LINE__ 文件当前行号
__DATE__ 文件被编译的日期
__TIME__ 文件被编译的时间
__STDC__ 如果编译器遵循ANSI C,其值为1,否则未定义
例如
#include<stdio.h>
int main()
{
int a = 0;
printf("%s\n%d\n%s",__FILE__,__LINE__,__TIME__);
return 0;
}
3.#define
Ⅰ #define 定义标识符
例如
注意 #define MAX 6 后面最好不要加分号,容易出错
Ⅱ #define 定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义宏(define macro)
宏的格式
#include<stdio.h>
#define ADD(X,Y) ((X)+(Y))
int main()
{
int a = 3;
int b = 2;
printf("%d",ADD(a,b));
return 0;
}
打印结果
注 所以用于对数值表达式进行求值的宏定义都应该用这种方式加上括号,避免在使用宏时由于参数中的操作符或邻近操作符之间不可预料的相互作用
Ⅲ #define 替换规则
在进行#define 定义符号和宏时 有以下步骤:
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先被替换
- 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值替换
- 最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程
注:宏,不能出现递归;
Ⅳ #和##
我们平时可能会有这样的问题:如何把参数插入到字符串中?
#include<stdio.h>
int main()
{
char* p = "hello""world!";
printf("hello""world!\n");
printf("%s",p);
return 0;
}
字符串是有自动连接的特点的
#include<stdio.h>
#define PRINT(FORMAT,VALUE) printf("the var is "FORMAT"",VALUE)
int main()
{
PRINT("%d",10);
return 0;
}
只有当字符串作为宏参数的时候才可以把字符串放在字符串中
使用# 可以把一个宏参数变成字符串
#include<stdio.h>
#define PRINT(FORMAT,VALUE) printf("the var of " #VALUE " is " FORMAT "\n",VALUE)
int main()
{
int a = 10;
PRINT("%d", 5+a);
return 0;
}
##的作用
##可以把位于它两边的符号合成一个符号
#include<stdio.h>
#define ADD_SUM(NAME,VALUE) (s##VALUE +=3)
int main()
{
int s = 0;
int s3 = 0;
printf("s3: %d\n",s3);
printf(" s: %d\n",ADD_SUM(s,3));
printf("s3: %d\n",s3);
return 0;
}
解析
注意 连接必须产生一个合法的标识符。否则其结果就是未定义的
四、宏和函数的区别
1.宏和函数的区别
刚开始学C语言的时候很多人会发现宏和函数好像
宏,通常被应用于执行简单的运算
例如 比较两个数的大小
#define MAX(X,Y) ((X)>(Y)?(X):(Y))
可能你回想为什么不用函数呢?
其实
- 调用函数涉及函数的栈帧调用,所用的时间比较多,所以说使用宏在程序的规模和速度要比函数好一些
- 函数的参数必须声明为特定的类型,宏是类型无关的
- 宏的参数可以是类型,而函数是不可以的
宏也有一些缺点
- 如果代码比较长,宏会增加程序的长度
- 宏是不方便调试的
- 宏会涉及到一些优先级的问题
- 宏是与类型无关的,不是很严谨
小建议:宏名尽量使用大写字母
2.#undef
用于移除一个宏定义
#undef NAME
//如果现存的一个名字需要被重新定义,那么它的旧名字首先要被移除
3.条件编译
我们在按住键盘的Ctrl 然后在点击头文件,会发现
会看到这些。
上图中的就是 条件编译
在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有 条件编译指令。
【例】调试性的代码,删除可惜,保留又碍事,所以我们可以选择性的编译
#include<stdio.h>
int main()
{
int a = 10;
int b = a;
//下方我们使用条件编译一下,查看赋值是否成功
#if 1
printf("%d",b);
#endif
return 0;
}
这里的 #if 就相当于 条件判断
#if是条件编译指令的关键字,可以根据条件判断是否编译某段代码。在编译预处理阶段,预处理器会根据#if后的条件判断语句的真假来决定是否编译接下来的代码。如果条件为真,则编译接下来的代码,否则忽略不编译
#endif 表示结束
#endif是C/C++中的一种预处理指令,表示条件编译的结束。它通常与#if或#ifdef一起使用,用于控制在编译时是否包含某段代码。如果#if或#ifdef指令中的条件为真,则编译区间代码,直到遇到#endif指令结束。
#ifndef
#ifndef 是一个预处理指令,表示如果指定的标识符已经被定义过了,则跳过后续的代码,否则执行后续的代码。在C/C++中,通常用来防止头文件被重复包含,因为头文件中可能包含全局变量、宏定义和函数声明等内容,多次包含会导致编译错误或运行时错误。因此,在头文件中一般都会使用#ifndef指令来避免重复包含。如果头文件被重复引入,会导致重复定义全局变量或函数,导致编译错误或者出现未知错误
所以使用条件编译可以避免头文件被重复引入
#include<stdio.h>
#define A 100
int main()
{
int a = 10;
int b = a;
#ifndef A
printf("A is not defined\n");
#endif
#ifndef B
printf("B is not defined\n");
#endif
#ifdef A
printf("A is defined\n");
#endif
return 0;
}
4.文件包含
当涉及多个代码文件时,我们就会使用头文件
很多人对于 #include <test.h> 和#include "test.h" 有疑问
首先 #include 指令可以使另外一个文件被编译
在使用 #include <test.h>
这是库文件包含,查找头文件直接去标准路径下去查找,如果找不到就提示编译错误
使用 #include "test.h"
本地文件包含, 查找策略:先在源文件所在目录下查找,如果该头文件未找到,编译器就像查找库函数头文件一样在标准位置查找头文件
5.嵌套文件包含
使用条件编译可以避免头文件的重复使用,这可以提高效率
上述就是对程序环境、预处理和宏简单地介绍了一下,后续还会补充滴
加油!