美文网首页
关于#ifndef / #define / #endif使用详

关于#ifndef / #define / #endif使用详

作者: 楚简约 | 来源:发表于2017-01-05 16:07 被阅读0次

    想必很多人都看过“头文件中的 #ifndef/#define/#endif 防止该头文件被重复引用”。但是是否能理解“被重复引用”是什么意思?是不能在不同的两个文件中使用include来包含这个头文件吗?如果头文件被重复引用了,会产生什么后果?是不是所有的头文件中都要加入#ifndef/#define/#endif 这些代码?

    其实“被重复引用”是指一个头文件在同一个cpp文件中被include了多次,这种错误常常是由于include嵌套造成的。比如:存在a.h文件#include "c.h"而此时b.cpp文件导入了#include "a.h" 和#include "c.h"此时就会造成c.h重复引用。

    头文件被重复引用引起的后果:

    有些头文件重复引用只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些,但是对于大工程而言编译效率低下那将是一件多么痛苦的事情。

    有些头文件重复包含,会引起错误,比如在头文件中定义了全局变量(虽然这种方式不被推荐,但确实是C规范允许的)这种会引起重复定义。

    是不是所有的头文件中都要加入#ifndef/#define/#endif 这些代码?

    答案:不是一定要加,但是不管怎样,用ifnde xxx #define xxx#endif或者其他方式避免头文件重复包含,只有好处没有坏处。个人觉得培养一个好的编程习惯是学习编程的一个重要分支。

    下面给一个#ifndef/#define/#endif的格式:

    ifndef A_H意思是"if not define a.h" 如果不存在a.h

    接着的语句应该#define A_H 就引入a.h

    最后一句应该写#endif 否则不需要引入

    例如:


    ifndef GRAPHICS_H // 防止graphics.h被重复引用

    define GRAPHICS_H


    而关于#define 宏定义也有很多种, 首先介绍下宏的一些基本东西

    ①程序第一步是在预编译之前会有一些操作, 例如删除反斜线和换行符的组合, 将每个注释用一个空格替代...,

    ②然后在进入预编译的时候, 会寻找可能存在的预处理指定(由#开头), 例如C中常用的#include, 或者oc中的#import, #define...很多(条件编译语句...)

    ③处理#define的时候,然后预处理器会从#开始, 一直到执行到第一个换行符(写代码的时候换行的作用), 自然, #define只会允许定义一行的宏, 不过正因为上面提到的预处理之前会删除反斜线和换行符的组合, 所以可以利用反斜线定义多行宏, 在删除反斜线和换行符的组合后, 逻辑上就成了一行的宏了

    ④宏作用在预编译时期, 其真正的效果就是代码替换, 而且是直接替换(内联函数!!!), 这个和函数有着很大的区别, 并且正因为是直接替换, 在使用的时候就会有一些的注意点了, 这个在后面会给出例子

    ⑤宏可以被称为 类对象宏, 类函数宏

    ⑥定义宏的语法很简单, 一个宏定义由三部分组成 , 三分部之间用空格分开, #define, 宏的名字, 主体 例如: 宏#define PI(宏的名字) 3.14(主体), 这里有个注意点就是, 宏的命名和普通的变量命名规则相同

    ⑦宏在预处理阶段只进行文本的替换(相当于把代码拷贝粘贴), 不会进行具体的计算(发生在编译时期)

    定义宏
    下面我们介绍一下,我们基本使用到的宏定义:

    ①#define PI 3.14 这是宏的最简单的定义了, 可能也是大家应用最广的, 就是使用宏来定义一些常量(消除魔法数字)或字符串..., 这一类可以被称为类对象宏, 方便代码阅读和修改, 使用的时候直接使用定义的宏的名字, PI, 那么预处理器就会将代码中的PI替换为3.14

    float computeAreaWithRadius(float r) {
    
    return PI * r * r;
    
    }
    

    ②#define log(x) printf("this is test: x = %d", x) 这是宏的第二类定义, 即类函数宏, 这一类的宏和函数类似的写法, ( )中可以写变量, 用作函数的参数, 不过, 这个和函数的区别是, 宏的参数不指定类型, 具体的参数类型在调用宏的时候由传入的参数决定(有点其他语言里的泛型的意思), 这个可以算是和函数相比的优点, 下面测试一些这个宏的使用, 结果你猜对了么?

    #define log(x) printf("this is test: x = %d", x)
    
    int main(int argc, const char * argv[]) {
    
    int y = 12;
    
    log(y); // 输出为  this is test: x = 12
    
    }
    

    ③#define log(x) printf("this is test: "#x" = %d", x), 这个定义中和上面的区别是使用了一个#运算符, #运算符被用于利用宏参数创建字符串, 区分一下和上面的结果

    #define log(x) printf("this is test: "#x" = %d", x)
    
    int main(int argc, const char * argv[]) {
    
    int y = 12;
    
    log(y);
    
    // 输出为  this is test: y = 12 (而不是 x = 12, 或者 12 = 12)
    
    // 因为使用#和参数结合可以被替换为宏参数对应的字符串, "#x"表示字符串x, 这里输入的参数为y, 则替换为y(不是12)
    
    log(2+4)// 输出为 this is test: 2+4 = 6
    
    }
    

    ④#define power(x) xx 这个和上面一样是一个类函数宏, 这里我原本的意愿是计算 xx即x的平方的值, 不过这样的定义宏在有些情况下是会出问题的, 这个例子就是告诉大家定义类函数宏的时候就真的要小心, 不然结果并不是我们预期的

    #define power(x) x*x
    
    int x = 2;
    
    int pow1 = power(x); // pow1 = 2*2 = 4
    
    int pow2 = power(x+1); //  pow2 = 3 * 3 = 9  ??
    
    // 显然对于pow1 = 4是没有问题的
    
    // 不过对于pow2 = 9 这个结果是有问题的, 定义的宏并没有达到我们预想的效果 结果为 3*3
    
    // 因为: 上面提到过宏是直接的代码替换, 这里宏展开后就成为了 x+1*x+1 = 2+1*2+1 = 5
    
    // 这里因为运算优先级的原因导致结果的不一样, 所以pow应该(加上括号)定义为
    
    #define power(x) (x)*(x)
    

    ⑤#define RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a] 这里是个简单的多参数的类函数宏的定义, 这个宏在使用OC开发的时候 大家可能都会喜欢使用

    ⑥这个宏是一个"多行宏"定义的示例, 即在除了最后一行的最后加上反斜线(因为反斜线和换行符的组合在预编译之前会被系统删除), 同时这个宏也说明了, 宏的定义是可以嵌套的(有些编译器可能不支持, xcode中是支持的...)

    #define RGB(r, g, b)  {\
    
    RGBA(r, g, b, 1.0f);\
    
    } 
    

    ⑦#define print(...) printf(VA_ARGS) 这个宏使用了两个新的东西...和VA_ARGS, 这两个是用来定义可变参数宏的, 可以看到是很简单的, 唯一一个注意点就是, ...要放在参数的最后, 如果你使用C定义可变参数的函数就会发现过程就很复杂了

    #define print(...) printf(__VA_ARGS__)
    
    int main(int argc, const char * argv[]) {
    
    print("测试可变参数 ---- %d", 12); // 输出结果为: 测试可变参数 ---- 12
    
    }
    

    ⑧#define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x; 最后一个宏介绍另外一个运算符 ## 这个是宏定义中的连接运算符, 例如上面的weak##x 就是将weak和参数x连接在一起, 同时这一个宏在iOS开发中是很有用的, 使用block的时候为了消除循环引用 通常使用weakSelf, 那么就可以定义这样一个宏, 而不用每次都输入上面一段重复的代码 __weak typeof(self) weakself = self, 那么上面定义的宏和这段代码一样会生成一个弱引用的新变量, 不过上面定义的时候使用了autoreleasepool{}, 这一个自动释放池本质上并没有什么用, 只不过对调用weakify会有影响, 需要使用@weakify(x), 😄看上去逼格更高, 不过在RAC中weakify是另外的方式定义的, (开篇给出的第九个宏定义)这个就可以自己下去研究一下了.

    #define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x;
    
    加上 autoreleasepool{}使用宏的时候就应该加上@
    
    像这样:
    
    - (void)delay {
    
    @weakify(self)
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    [weakself test];
    
    });
    
    }
    
    当然如果你没有加autoreleasepool{}, 使用宏就不用加上@了
    
    #define weakify( x ) __weak typeof(x) weak##x = x;
    
    像这样:
    
    - (void)delay {
    
    weakify(self)
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    [weakself test];
    
    });
    

    相关文章

      网友评论

          本文标题: 关于#ifndef / #define / #endif使用详

          本文链接:https://www.haomeiwen.com/subject/gnyzvttx.html