define 面试知识点都在这里了!

Posted Linux猿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了define 面试知识点都在这里了!相关的知识,希望对你有一定的参考价值。

 作者:Linux猿

 简介:CSDN博客专家,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!

 关注专栏:C/C++面试通关集锦 (优质好文持续更新中……)

define 是预处理器的一个指令,定义在 C 语言中。在预处理过程中,将宏替换为对应宏值,可以理解为字符串的替换。

为什么说它是预处理器的一个指令呢?

我们来看一个简单的例子:

#include <stdio.h>
#define x 5
int main() {
    int y = x + 3;
    printf("y = %d\\n", y);
}

 在上面的代码中,定义了一个宏 x ,其值为 5,我们只将代码预处理一下,执行如下命令:

gcc -E -o main.i main.c

预处理:完成头文件的插入、宏定义的展开以及条件编译的处理等。

执行命令后,在当前目录下生成了文件 main.i,部分内容如下所示:

......
......
# 4 "main.c"
int main() {
    int y = 5 + 3;
    printf("y = %d\\n", y);
}
......
......

可以看到,宏 x 在预处理后已被替换为 5了。

当然,宏定义也可以是更复杂的形式,比如:表达式、函数等,来看一个例子:

#include <stdio.h>

#define MAX(x, y) ((x) > (y)) ? (x) : (y)
int main() {
    printf("max = %d\\n", MAX(2+3, 4+5));
}

输出结果为:

linuxy@linuxy:~/defineDir$ ./main 
max = 9
linuxy@linuxy:~/defineDir$

来看一下预处理后的结果(main.i文件部分内容):

......
......
# 4 "main.c"
int main() {
    printf("max = %d\\n", ((2+3) > (4+5)) ? (2+3) : (4+5));
}
......
......

可以看到直接将定义的宏进行了替换。

但是,这个替换可能会导致如下问题:

问题 1. define 并不做类型检查

define 只做值替换,而不像普通变量那样做类型的检查。

来看一个例子:

#include <stdio.h>

#define PLUSONE(x) ++x 
int main() {
    int valInt = 10;
    double valDou = 20;
    char *p = "LinuxY";
    printf("valInt = %d\\n", PLUSONE(valInt));
    printf("valDou = %lf\\n", PLUSONE(valDou));
    printf("p = %s\\n", PLUSONE(p));
}

输出结果为:

linuxy@linuxy:~/defineDir$ gcc main.c -o main
linuxy@linuxy:~/defineDir$ ./main 
valInt = 11
valDou = 21.000000
p = inuxY
linuxy@linuxy:~/defineDir$ 

可以看见,PLUSONE(x) 可以接受任何类型的参数 x,这就加大了程序出错的风险。

问题 2. define 的宏直接替换导致的问题

宏的替换可以说是字符串的直接替换,所以会导致下面这个问题。

#include <stdio.h>

#define MULTIPLY(x, y) x * y
int main() {
    printf("result = %d\\n", MULTIPLY(2, 3 + 4));
}

输出结果为:

linuxy@linuxy:~/defineDir$ gcc main.c -o main
linuxy@linuxy:~/defineDir$ ./main 
result = 10
linuxy@linuxy:~/defineDir$ 

是不是有点惊讶!按照正常的理解输出结果应该为14,但是这里结果却是10,其实程序是这样计算的:

宏替换后,2 * 3 + 4 = 10,即:直接将 x 替换为 2,y 替换为 3 + 4。而不是将 y 替换为 7。

为了证明,我们来看下预处理后的程序,执行命令:

linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c

预处理后的 main.i 文件有如下内容:

......
......
# 4 "main.c"
int main() {
    printf("result = %d\\n", 2 * 3 + 4);
}
......
......

这下明白了吧!

那么,如何避免上面这种情况呢 ?

需要添加括号,规定好优先级,修改后如下所示:

#include <stdio.h>

#define MULTIPLY(x, y) (x) * (y)
int main() {
    printf("result = %d\\n", MULTIPLY(2, 3 + 4));
}

输出结果为:

linuxy@linuxy:~/defineDir$ gcc main.c -o main
linuxy@linuxy:~/defineDir$ ./main 
result = 14
linuxy@linuxy:~/defineDir$ 

接下来再说一下 define 的一个容易理解错误的点。

我们通常意义上的理解是宏定义了就不能更改,事实上真是这样吗?

我们来看一个简单例子:

#include <stdio.h>

#define x 5
int main() {
    printf("x = %d\\n", x);
    #undef x
    #define x 10
    printf("x = %d\\n", x);
}

输出结果为:

linuxy@linuxy:~/defineDir$ gcc main.c -o main
linuxy@linuxy:~/defineDir$ ./main 
x = 5
x = 10
linuxy@linuxy:~/defineDir$

看到了吗?x 值变化了!我们来看一下预处理后的结果,执行命令:

linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c 

文件 main.i 中可以看到源代码部分内容为:

......
......
# 4 "main.c"
int main() {
    printf("x = %d\\n", 5);


    printf("x = %d\\n", 10);
}
......
......

x 分别被替换为 5 和 10,是不是很意外!

接着上面这个问题,再说一下条件编译。

经常在条件编译中看到 #define 的身影,通常在一个头文件中会有如下定义:

#ifndef ADD_H

#define ADD_H

//头文件内的语句


#endif // ADD_H

上面语句的意思是:如果还没有定义 ADD_H,就执行 #ifndef ADD_H 后的内容。

这样可以防止一个头文件被多次包含,重复引用。

当然,还有其它的条件编译指令,如下所示:

(1)#if

检测表达式值是否为真。如果为真,则编译后面的代码直到出现 #else、#elif 或 #endif 为止,否则不编译。类似于 if 语句。

(2)#else

当#if #ifdef #ifndef 指令不满足的时候,就执行 #else 后面的代码。

(3)#elif

类似于 else if 语句,后面接一个表达式进行判断。

(4)#ifdef

判断宏是否定义,如果已定义,则执行后面的语句。

(5)#ifndef

判断宏是否未定义,如果未定义,则执行后面的语句。

(6)#endif

用于终止 #if #ifdef #ifndef 指令。

(7)#undef

用于取消宏的定义,上面已举例说明。

上面的指令大多和 if else 等语句有类似的功能,但是,上面的指令除 #undef 外,其它指令必须有结束标志 #endif。

当然,条件编译还有其它的用途,比如:跨平台、调试的情况。

最后,说一下 # 开头的预处理指令。

在文件中以 # 开头的预处理指令,都是在预处理环节处理的,上面已经看了宏的预处理,接下来再看一个简单例子:

#include <stdio.h>

int main() {
    printf("Hello World!");
}

预处理一下上面的代码,执行命令:

linuxy@linuxy:~/defineDir$ gcc -E -o main.i main.c

查看 main.i 文件,部分内容如下所示:

extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__));
# 840 "/usr/include/stdio.h" 3 4
extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));



extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ;


extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__));
# 858 "/usr/include/stdio.h" 3 4
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);
# 873 "/usr/include/stdio.h" 3 4

可以看到头文件 #include<stdio.h> 被展开了。

总结

好了,define 的知识点就讲解完了,希望对大家有帮助!

关注专栏:C/C++面试通关集锦 (优质好文持续更新中……)

以上是关于define 面试知识点都在这里了!的主要内容,如果未能解决你的问题,请参考以下文章

寒冬也挡不住进大厂的决心,Android面试知识架构,面试需要掌握的都在这里!

JSP面试题都在这里

动态代理总结,面试你要知道的都在这里,硬核干货无废话

2019校招Java 面试题:百度前200页都在这里了

程序员面试30题,你面试失败的原因都在这里!

面试会问的Spring事务相关的都在这里了