C++ #if #endif #define #ifdef #ifndef #if defined #if !defined详解
先了解一下 预处理命令:
在编译之前进行得处理,C语言得预处理主要有三个方面得内容:1.宏定义;2.文件包含;3.条件编译。预处理命令是以“#”符号开头的。
常见符号意义
#if //编译预处理中的条件命令,相当于C++中的if语句
#define //定义一个预处理宏
#undef //取消宏定义
#ifdef //判断某个宏是否被定义
#ifndef //判断某个宏是否未被定义,与#ifdef相反
#elif //当#if, #ifdef, #ifndef或前面的#elif条件不满足时,执行当前#elif之后的语句,相当于C++中的else-if
#else //与#if, #ifdef, #ifndef对应, 当这些条件都不满足时,执行#else之后的语句,相当于C++中的else
#endif //#if, #ifdef, #ifndef这些条件命令的结束标志,成对使用
defined //与#if, #elif配合使用,判断某个宏是否被定义
1、宏定义
可以对类型重命名,例如:
#define PI 3.141592654
NOTE:
符号常量虽然可以提高代码的可读性和可维护性,如果要修改它的值,只需定义修改一次,然后重新编译即可。但是有可能会与程序在其他地方使用的变量冲突,建议使用const关键字代替#define,例如:
#define n_def 5
const int n_con = 7;
void test()
{
int n_def;
int n_con;
}
预处理器将把int n_def替换为int 5;从而导致了编译错误,而test()中定义的n_con是本地变量。
宏可以带参数(也就是宏函数),#define 标识符(x1,x2…) 替换列表,注意标识符和(之间不能有空格,例如:
#define DeletePtrArr(p) if ((p)!=NULL) { delete[] (p);(p)=NULL;}
#define DeletePtr(p) {if(NULL!=p){delete p; p = NULL;}}
#define max(Var1,Var2) (Var1>Var2? Var1:Var2)
NOTE:
使用宏函数的时候,效率很高,这也是使用其的一个主要原因。但是也存在一些缺点:①仅仅做预处理器符号表的简单替换,因此它不能进行参数的有效性检验,另外它的返回值不能被强制转换为可转换的合适的类型,宏定义时还要注意书写(参数要括起来),否则容易出现歧义,宏定义只是在形式上类似函数;②宏不能访问对象的私有成员;③宏定义任何时候都是执行严格的参数替换策略,容易产生二义性,例如在使用++、–等操作符的时候可能会出现问题。
为了解决宏定义的以上问题,c++中引入了内联函数(在函数前面加上inline),即引入inline关键字,在能使用宏定义的地方都能够使用inline函数;inline函数也没有调用的开销,它会进行数据类型检查, 因为inline函数是真正的函数;inline函数还可以使用类的保护成员以及私有成员,因为inline函数可以作为类的成员函数;另外inline函数在运行时可以调试,而宏定义不能。一般在c++中建议用内联函数代替宏
举个例子1:(参考:https://blog.csdn.net/lyrebing/article/details/24018971)
#include <iostream>
using namespace std ;
#define Max( a , b ) ( a > b ? a : b); //宏定义,在此处#define之后加了; 一般不要加;下面解释
inline int fun( int a , int b ) //内联函数
{
return ( a > b ? a : b ) ;
}
int main()
{
int a , b ;
a = 3 ;
b = 0 ;
Max( ++a , b );
cout << a << endl ;
a = 3 ;
b = 5 ;
Max( ++a , b );
cout << a << endl ;
a = 3 ;
b = 0 ;
fun( ++a , b ) ;
cout << a << endl ;
return 0 ;
}
以上输出的结果为: 5 4 4
还应注意与typedef的区别
type是在编译时处理的,它是在自己的作用域内给一个已经存在的类型一个别名,例如:
typedef unsigned char Byte;
而对于以下:
#define ptr int *
ptr a, b; //相当于int * a, b; 只是简单的宏替换
typedef int* ptr;
ptr a, b; //a, b 都为指向int的指针,typedef为int* 引入了一个别名
以上两个看似效果相同,但是差别很大,第一个只是简单的进行了替换,相当于语句int * a, b;表示定义了一个整型指针a和整型变量b。
另外注意到typedef语句最后有“;”,而define不加分号,因为#define不是语句,如果加了分号会连分号一块置换,对于上述例子1中的#define Max( a , b ) ( a > b ? a : b); 最后出现了分号,是因为在这里相当于宏定义了一个函数,在替换的时候也会一起替换,为了后面语句的可执行性,当然此处的;省略也不会发生错误,此外在主程序中Max(++a,b)之后不加;也是可以执行的。
2、文件包含
#include "file"
#include <file>
使用双引号,系统首先到当前目录下查找被包含的文件,如果没找到,再到系统指定的"包含文件目录"(由用户在配置环境时设置)去找;而使用尖括号时,系统会直接到系统指定的"包含文件目录"去查找。通常使用双引号比较保险。
3、条件编译
常量表达式:
#define CONDITION
#if CONDITION
Printf(“Value of i:%d\n”, i);
#endif
#if 常量表达式,常量表达式为0时,预处理器会删除#if 和#endif中间的代码,并且#if 会把没有定义过的标准符视做为0,如果没有定义CONDITION, 则测试#if CONDITION会失败,但#if !CONDITION会成功,即如果常量表达式为一个没有定义的宏,它的值会被视作0。
取消已经定义的宏:
#if defined VALUE //检验VALUE是否被定义
#undef VALUE //如果已经被定义,解除定义
#define VALUE 77 //重新定义VALUE为77
#endif
检验是否被定义,还可以写成:
#ifndef VALUE //如果VALUE没有被定义
#define VALUE 77 //定义VALUE 为77
#endif
防止重复inclue:
方式1:
#if !defined __MYHEADER_H__
#define __MYHEADER_H__
#endif
方式2:
#ifndef __MYHEADER_H__
#define __MYHEADER_H__
#endif
方式3:(#pragma once)
#pragma once
#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的状况
#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。