C语言预处理——

预定义符号

FILE

代表了当前代码所处的地方,即当前代码的绝对路径。

LINE

代表了当前行号

TIME

可以显示当前时间

DATE

显示今天日期

FUNCTION

显示代码所处函数名

利用这些预定义符号,我们可以写日志,如下

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i;
	FILE* pf = fopen("log.txt", "w");
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
		fprintf(pf, "%s\t%s\t%s\t%d\t%s\t%d\n", __DATE__, __TIME__, __FILE__, __LINE__, __FUNCTION__,arr[i]);
	}
	return 0;
}

#define

以#开头的都是预处理指令

#define定义标识符

可以是数,也可以是字符串等各种内容

#define reg register //用更加简单的符号来代替

#define do_forever for(;;) //用更形象的符号代替死循环

#define定义宏

#define 名称(参数列表) 表达式

其中名称和后面的括号要紧挨着,不可以有空格。例如:

但注意,下面代码有bug

为什么不是16和60呢?这是因为宏定义是文本替换

第一个被替换成3+1*3+1,即3+3+1=7,所以启示我们把每个变量单独加上括号

第二个被替换成10*3+3,即30+3=33,所以启示我们把整个表达式加上括号

如下:

注意

当预处理器搜索#define定义时,字符串中的符号不被替换

cpp 复制代码
#define MAX 10;
printf("MAX=%d", MAX);

" "里面的内容时字符串常量,不被替换。

接下来就是#和##,它们在#define中有特殊用处。注意,是仅限于宏定义中

这里的#,##不是define前面的,而是一个单独的符号

#将符号变成字符串

对于下面的代码

我们是想打印出the value of a is 10 the value of b is 20,这就可以用到#。如果是单纯的宏定义#define PRINT(x) printf("the value of x is %d\n",x) 那结果和上面一样,那如何将""中的x变成传过去的a或b呢?可以用#。

#a就是"a"

同时要明确printf("hello world")和printf("hello" " world")答应出来结果一样

所以宏定义可写成#define PRINT(x) printf("the value of #x is %d\n",x),对吗?不对,如果这样写,那就相当于 printf("the value of "x" is %d\n",x),这样的话the value of 和 is %d\n 就不是完整的字符串了,所以应该#define PRINT(x) printf("the value of "#x" is %d\n",x)在#x前后都用引号引住。

注意of 后面和 is前面都要加空格。

##将分割的字符串连在一起

宏和函数的对比

宏通常用于一些简单的运算

这个比大小既可以用函数也可以用宏,但用宏更好

优缺点

1.如果a和b是float类型,直接调用Max函数就会出错,因为浮点数的存储和整型不同,这时候就得更改函数的定义了,很麻烦,而宏定义就不用更改

2.在Max函数调用时会有函数调用和返回的消耗(为了调用函数,需要做很多准备工作),需要很多工作才能执行到return (x > y ? x : y);而宏就不需要,它在预处理阶段就完成了替换,可以直接执行(x > y ? x : y)。

但宏也有劣势

1.宏没法进行调试

2.如果宏代表的代码特别长,而main函数中多次运用了该宏,那么预编译完后,代码会特别长

3.宏与类型无关,其实不够严谨

4.带有副作用的宏参数

cpp 复制代码
int Max(int x, int y)
{
	return x > y ? x : y;
}
int main()
{

	int a = 10;
	int b = 20;
	int max1 = Max(a++, b++);
	printf("%d\n ", max1);
    printf("%d\n ", a);
    printf("%d\n ", b);
	return 0;
}

对于这个,用函数比大小,结果是20 11 21。也就是说,再将参数传给函数前,系统会把表达式算出来再使用,而对于后置++则是先使用再++,而宏就不一样了

这是因为宏是把整个表达式看作一个整体进行替换,变成了((a++)>(b++)?(a++):(b++)),所以当真正执行到?或:前,++操作已经进行了一次,所以导致结果出乎意料,这种参数就是带有副作用了。

函数无法实现而宏可以实现的

宏的参数可以是类型而函数不可以,例如:

再比如malloc函数,int* p = malloc(10 * sizeof(int));这样开辟空间就看着十分别扭,此时就可以用到宏,如下

cpp 复制代码
#define MALLOC(num,type) (type*)malloc(num * sizeof(type))
int* p = MALLOC(10, int);

这样会使代码更好看好理解。

#undef

用于移除一个宏定义

报错说第8行的max未定义。

命令行定义

在Linux环;境下,代码中可以有未定义的符号,之后在预编译阶段去指定其大小,如下

cpp 复制代码
int main()
{
	int arr[SZ] = { 0 };
	int i;
	for (i = 0; i < SZ; i++)
	{
		arr[i] = i;
	}
	return 0;
}

编译指令:gcc test.c -D SZ=10

gcc test.c表示编译.c文件,-D是说要给一个参数

条件编译

对于某些调试性代码,删掉可惜,保留又碍事,我们就可以用条件编译语句来进行选择性编译,让其还存在于源代码中,但在预编译阶段被删去

cpp 复制代码
#define DEBUG
int main()
{
	int arr[10] = { 0 };
	int i;
	for (i = 0; i < 10; i++)
	{
		arr[i] = i;
#ifdef DEBUG
		printf("%d ", arr[i]);
#endif
	}
	return 0;
}

#ifdef DEBUG 表示如果定义了DEBUG,那么printf语句就执行,#endif(结束条件编译)是和#ifdef配套使用的。

而在上面的代码中,有#define DEBUG(后面可以不写它的值),所以会打印

如果没有#define,就不打印

其实这段代码在预编译阶段,#ifdef printf("%d ", arr[i]) #endif这三个语句就被删除了

常见的条件编译指令

单分支

#if 常量表达式

//......

#enif

if后面的常量表达式为真,则代码执行,如下,1为真,所以打印

#define MAX 值

#if MAX

//......

#endif

因为MAX的值为0,表示假,所以不打印

多分支

#if 常量表达式

//......

#elif 常量表达式

//......

#else

//......

#endif

判断是否被定义

#if defined(DEBUG)

printf("hehe");

#endif

表示如果定义过,就打印,等价于下面的代码

#ifdef DEBUG

printf("hehe");

#endif

#if !defined(DEBUG)

printf("hehe");

#endif

表示如果没有定义过,就打印,等价于下面的代码

#ifndef DEBUG

printf("hehe");

#endif

头文件的包含

例如

#include<stdio.h>

#include"test.h"

一般来说,尖括号包含的是库文件,双引号包含的是自己写的头文件。原因如下:

若用了双引号则会先在源文件所在的目录下查找,若未找到,则编译器像查找库函数头文件一样到标准路径下查找,若未找到则报错;若用了尖括号,则直接到标准路径下查找。

既然知道自己写的头文件一定在源文件所在目录下,那就直接用双引号。

如何防止多次包含

如果多个文件嵌套包含,很可能出现一个头文件被包含多次,导致预编译之后代码的冗余,同时降低代码运行效率。我们可以通过Linux环境看到这个效果

指令:gcc test.c -E > test.i 表示先进性预编译,编译后放到test.i文件中

接着用 vim test.i 可以查看test.i文件。如果stdio.h文件被包含了3次,我们就可以看到三段重复的代码。

那如何防止多次包含?

方法一:预编译指令

cpp 复制代码
#ifndef __TEST__H__
#define __TEST__H__

int ADD(int x, int y);

#endif

在头文件test.h里面如上这样写,如果没定义过DEBUG,下面的代码就执行,函数ADD就可以被声明;当再次遇到这个头文件时,DEBUG已经被定义过了,所以#ifndef DEBUG为假,后面的语句就不再执行,ADD函数就不会被重复声明。

注意,一般ifndef后面的符号是把头文件大写并加上下划线

方法二:#pragma once

cpp 复制代码
#pragma once
int ADD(int x, int y);

在test.h中这样写,则只包含一次

相关推荐
wn53124 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
青椒大仙KI1127 分钟前
24/9/19 算法笔记 kaggle BankChurn数据分类
笔记·算法·分类
^^为欢几何^^31 分钟前
lodash中_.difference如何过滤数组
javascript·数据结构·算法
豆浩宇31 分钟前
Halcon OCR检测 免训练版
c++·人工智能·opencv·算法·计算机视觉·ocr
Hello-Mr.Wang36 分钟前
vue3中开发引导页的方法
开发语言·前端·javascript
救救孩子把39 分钟前
Java基础之IO流
java·开发语言
WG_1740 分钟前
C++多态
开发语言·c++·面试
宇卿.1 小时前
Java键盘输入语句
java·开发语言
浅念同学1 小时前
算法.图论-并查集上
java·算法·图论
何不遗憾呢1 小时前
每日刷题(算法)
算法