文章目录
- 程序的翻译环境:
- 程序的运行环境:
- C语言预定义符号
- #define定义标识符
- #define定义宏
- 具有副作用的宏参数
- #与##
- #的使用
- ##的使用
- 宏和函数对比
- #undef
- 命令行定义
- 条件编译
- 常见的条件编译指令🌞
- 文件包含指令
- 嵌套文件包含
- 其他预处理指令
- 撒花
程序的翻译环境:
整个翻译环境很复杂,简单形容:
我们运行一个c语言代码,首先会将代码进行转化,将c语言代码转化为汇编代码,再将汇编代码转化为电脑看的懂的二进制语言,项目下的所有文件都会进行上述操作,之后进行链接,将文件链接到一起。
程序的运行环境:
程序执行过程:
1. 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序 的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
2. 程序的执行便开始。接着便调用main函数。
3. 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回 地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程 一直保留他们的值。
4. 终止程序。正常终止main函数;也有可能是意外终止。
C语言预定义符号
这些预定义符号可以直接进行使用
__ FILE __ //进行编译的文件路径
__ LINE __ //此文件内当前行号
__ DATE __ //编译时日期
__ TIME __ //编译时时间
__ STDC __ //如果编译器遵循ANSI C ,值为1,否则未定义
代码示例
#include <stdio.h>
int main()
{
printf("当前文件路径:%s\n",__FILE__);
printf("当前行号:%d\n",__LINE__);
}
#define定义标识符
#define name stuff
程序在预处理的时候会将stuff替换回来,那为什么还要使用define?
- 方便修改,如果有一个值我们经常会用到,那么如果需要修改时需要一个一个进行修改,使用#define后每次只要修改define后面的值即可
- 如果某个关键字或者程序语法太长可以使用#define将其改成一个非常简短的名字
stuff位置可以写成任何东西,只要符合语法即可⭐
#define MAX 100
#define do_forever for(;;)
#define ll long long
定义#define的时候尽量不要加上;从语法上来说并没有错误,不过容易引起错误。
代码示例
#define MAX 100;
int main()
{
printf("%d",MAX);
//由于定义的时候100后面加了分号,替换过来就变成↓
//printf("%d",MAX;); 所有不要这么写
}
#define定义宏
#define name(参数) 参数操作
当程序进行到预处理阶段的时候就会将name(参数)部分替换回”参数操作部分“;
示例
#define ADD(a,b) a+b
int main()
{
int a = 8;
int b = 10;
printf("%d",ADD(a,b));
}
//预处理过后
int main()
{
int a = 8;
int b = 10;
printf("%d",a+b); //18
}
定义宏的时候一定要注意宏是直接替换到程序中的,容易引发一些小问题
#include<stdio.h>
#define ADD(a,b) a+b
int main()
{
int a = 9;
int b = 5;
printf("%d", 10 * ADD(a, b));
}
//这段程序我们的理想结果是50,可是运气起来结果却是23
//预处理过后。。。。。
#include<stdio.h>
#define ADD(a,b) a+b
int main()
{
int a = 9;
int b = 5;
printf("%d", 10 * a+b);
}
可以看到直接将宏替换过来后先进行10与a的运算再+b
为了避免这种情况的发生,可以将宏内的运算加上括号
#define ADD(a,b) (a+b)。
这样就可以有效避免问题的发生🤓
还有这种情况:
#include<stdio.h>
#define SQUARE(x) x*x
int main()
{
int a = 8;
printf("%d",SQUARE(a+1));
}
乍一看觉得这代码输出为81,实际上结果为17
替换过后。。。。。
int a = 8;
printf(“%d”,a+1*a+1);
很显然结果为17.。。
要避免这种情况也可以将表达式加括号
#define SQUARE(x) ((x)*(x))
具有副作用的宏参数
使用宏的时候要避免 ‘’ ++ ‘’ “ - - ”这种操作符会引起不可预测的结果,要尽量是用x+1,x-1着这种形式。⭐
#与##
#的作用是将一个宏参数变成字符串
- 使用方法:#参数
##的作用是将##两边的参数进行连接形成一个新的标识符(此标识符必须得存在)
- 使用方法:参数##参数
#的使用
#include <stdio.h>
int main()
{
printf("好好学习,""天天向上"); //字符串是可以自动连接的,”好好学习,天天向上“
}
#include <stdio.h>
#define print(VALUE) \
printf("边长为"#VALUE"的正方形周长为%d",VALUE * VALUE)
int main()
{
print(3); //输出:边长为3的正方形周长为9
}
##的使用
int CLASS = 100;
#define ADD_TO_SUM(num,value) num##value;
int main()
{
ADD_TO_SUM(CL, ASS); //CLASS
}
##连接的两个参数必须组成一个合法标识符否则就是未定义的
宏和函数对比
/ | 宏 | 函数 | 获胜 |
---|---|---|---|
速度 | 更快 | 存在调用和返回,比宏要慢 | 宏 |
可调式性 | 宏不可调试 | 函数可调式 | 函数 |
类型 | 宏不区分类型 | 区分类型 | 宏/函数(各有各的好) |
代码长度 | 每次使用时,宏代码都会被插入到程序中。除了非常 小的宏之外,程序的长度会大幅度增长 | 函数代码只出现于一个地方;每 次使用这个函数时,都调用那个 地方的同一份代码 | 函数 |
递归 | 不可递归 | 可递归 | 函数 |
具有副作用的参数 | 参数可能被带到宏内各各位置,会产生不可预料的结果 | 函数的参数在传参时求值一次,更容易控制结果 | 函数 |
一般来说函数用得较多,当然宏也有自己的优势,在适合的场景可以使用宏。
命名:为了区分宏与函数,一般宏名我们全部大写,函数名不要全部大写
#undef
#undef的作用是移除宏定义
int main()
{
#define MAX 100
printf("%d",MAX);
#undef //为防止后面标识符冲突,进行移除
int MAX = 101;
printf("%d",MAX);
}
命令行定义
我们可以在命令行中进行编译的时候给代码内某一标识符进行赋值
条件编译
在编译一条语句的时候我们可以进行判断某个值或宏有没有在上面定义,如果有定义执行,没有定义则不执行相当于注释掉。
#define MAX(a,b) a+b
int main()
{
int a = 2;
int b = 3;
#ifdef MAX
printf("%d\n", MAX(a, b));
#endif
return 0;
}
常见的条件编译指令🌞
-
#if 常量表达式
………
#endif -
#if 常量表达式
………
#elif 常量表达式
………
#else
………
#endif -
#if define(标识符)
#ifdef 标识符 -
#if !define(标识符)
#ifndef 标识符
以上条件编译指令也可以进行嵌套使用
文件包含指令
#include 可以对其他文件进行包含,从而使用其他文件内的函数或全局变量
-
文件包含的两种方式
- 本地文件包含 #include ”filename“
- 库文件包含 #include < filename >
两种不同的包含方式在查找文件的时候有所不同,本地文件包含,先到源文件所在目录下查找如果未找到,到查找库文件的位置查找,库文件包含就直接到查找库文件的位置进行查找
显然本地文件包含无论比较万能,但那也不代表所有的包含都要用本地文件包含,为了效率考虑还是看实际文件是不是库函数,是库函数就用库文件包含,不是的话就用本地文件包含。
嵌套文件包含
在文件开头写 #pragma once即可解决
其他预处理指令
#error
#pragma
#line
.........
撒花
这就是文件操作的全部内容了,创作不易,还请各位小伙伴多多点赞👍关注收藏⭐,以后也会更新各种小游戏还有关于c语言的博客,撒花!