C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
https://developer.aliyun.com/article/1499018
运行环境
程序执行的过程:
- 程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
- 程序的执行便开始。接着便调用main函数。
- 开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程一直保留他们的值。
- 终止程序。正常终止main函数;也有可能是意外终止。
预处理详解
__FILE__ //进行编译的源文件 __LINE__ //文件当前的行号 __DATE__ //文件被编译的日期 __TIME__ //文件被编译的时间 __STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
在vs2019中__STDC__ 不支持
#include<stdio.h> #define M 100 int main() { printf("%s\n", __FILE__);//查看当前的文件路径 printf("%d\n", __LINE__);//查看这一行是第几行 printf("%s\n", __DATE__);//查看当前日期 printf("%s\n", __TIME__);//查看当前时间 return 0; }
在linux的gcc可以
#define
(1)定义常量
#define M 100
(2)定义宏
#define 机制包括了一个规定,允许把参数替换到文本中,这种实现通常称为宏(macro)或定义
宏(define macro)。
#define name( parament-list ) stuff
其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中。
#define M(a,b) (a+b)
我们尽量在写宏的时候给替代的数值加个()
#define 替换规则
在程序中扩展#define定义符号和宏时,需要涉及几个步骤。
- 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号。如果是,它们首先
被替换。
#define a 100 #define M(a,b) (a+b) int main() { printf("%d", M(a, 2)); return 0; }
2.替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被他们的值所替换。
3.最后,再次对结果文件进行扫描,看看它是否包含任何由#define定义的符号。如果是,就重复上述处理过程。
注意:
4.宏参数和#define 定义中可以出现其他#define定义的符号。但是对于宏,不能出现递归。
5.当预处理器搜索#define定义的符号的时候,字符串常量的内容并不被搜索。
#和##
我们知道在字符串中有#define定义的符号是不会替换的
#
使用 # ,把一个宏参数变成对应的字符串
#define print(a, format) printf("the value of " #a " is " format "\n", a)
#include<stdio.h> #define PRINT(a, format) printf("the value of " #a " is " format "\n", a) int main() { int b= 10; PRINT(b, "%d"); return 0; }
#会把宏参数变成一个字符串,不会进行转换
##
##可以把位于它两边的符号合成一个符号。
它允许宏定义从分离的文本片段创建标识符。
#include<stdio.h> //#define A 100 //#define M(a,b) (a+b) #define ADD(num, value) int sum##num = value #define print(num) printf("%d ", sum##num) int main() { int i = 0; for (i = 0; i < 5; i++) { ADD(i, i); print(i); } return 0; }
我们可以使用于创建一些文件名的地方,或者创建一些变量
带有副作用的宏参数
#include<stdio.h> //#define a 100 //#define M(a,b) (a+b) #define ADD(a,b) (a >= b? a + b : 1) int main() { int a = 10; int b = 10; printf("%d\n", ADD(a++, b)); printf("a = %d", a); return 0; }
可以看到我们写了一段代码里面有宏,传入的值是a++,下面预处理的代码如下
可以简单明了的看到a的值变化了两次
注意:我们传入参数要思考好,不然就会引起一些不必要的后果
宏和函数的比较
宏:
1.通常被应用于执行简单的运算
宏仅仅只有运算,
如果使用函数,不仅要为函数创建栈帧,参数的传递,还有运算, 最后函数返回,这个就会很费时间
2.宏比函数在程序运算的规模和速度更胜一筹
3.函数的参数要声明类型,而宏不是
函数:
1.宏每调用一次就会替换一次,如果宏很长,即不好写,也不好直观
2.宏不能调试,
3.宏的类型无关,也会不严谨
4.宏可能会造成一些运算顺序问题,戴上()频繁
建议:逻辑简单,使用宏,逻辑复杂使用函数
函数和宏的命名规则
把宏名全部大写
函数名不要全部大写
undef
移除一个宏定义
#include<stdio.h> #define M 100 int main() { #undef M printf("%d", M); return 0; }
M不存在了,这里就会报错.
命令行定义
在linux系统中
#include<stdio.h> int main() { int arr[sz]; int i = 0; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d", arr[i]); } return 0; }
条件编译
我们在写了一段代码发现,某些代码是不需要的,但是删除了可惜,因此我们在编译一个程序的时候我们如果要将一条语句(一组语句)编译或者放弃是很方便的。因为我们有条件编译指令。
#if … #endif
#if 常量表达式 //... #endif
相当于我们的if判断 ,没有else
#include<stdio.h> #define M 3 int main() { int a = 1; scanf("%d", &a); #if M==3 printf("%d", a); #endif printf("跳过"); return 0; }
如果M==3就执行printf(“%d”, a);,不是就不执行,
多分支语句
#if 常量表达式 //... #elif 常量表达式 //... #else //... #endif
#include<stdio.h> //#define a 100 //#define M(a,b) (a+b) #define M 10 int main() { #if M == 1 printf("1 "); #elif M == 2 printf("2 "); #else printf("3 "); #endif printf("运行结束"); return 0; }
这个语句相当于我们的if的多分支语句
判断是否被定义
#include<stdio.h> //#define a 100 #define M 0 int main() { #if defined(M) printf("定义了"); #endif printf("哈哈哈"); return 0; }
#include<stdio.h> //#define a 100 #define M 0 int main() { #ifdef M printf("定义了"); #endif printf("哈哈哈"); return 0; }
#if defined(M) 等同于 #ifdef M
#include<stdio.h> //#define a 100 //#define M 0 int main() { #ifdef M printf("定义了"); #elif !defined(M) printf("没有定义"); #endif printf("哈哈哈"); return 0; }
#if !defined(M) 等同于 #ifndef M
defined() 函数用于检查某个标识符是否已经被 #define 定义
文件包含
我们知道#include可以引入头文件
有两种表示形式
(1)包含本地文件(自己写的文件,或者别人写的)
#include"xxx.h"
会先源文件的目录下查找头文件,如果找不到就会到标准位置找(库函数的目录)(标准库位置)
(2)包含标准库的方式
#include<xxx.h>
直接到标准库里面找,找不到就报错
Linux环境的标准头文件的路径:
/usr/include
嵌套文件包含
我们在引入头文件可能会引入多次相同的文件
为了防止重复引用
#ifndef __TEST_H__ #define __TEST_H__ //头文件的内容 #endif //__TEST_H__
这段代码是经典的头文件保护机制,用于防止同一头文件被多次包含。当 TEST_H 没有被定义时,会定义它并包含头文件内容,否则直接跳过。
或者
#pragma once