预定义符号
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中这样写,则只包含一次