C之宏定义(十九)

我们在 C 语言中经常会用到宏定义,那么我们今天就对宏做个简单的介绍。#define 是预处理期处理的单元实体之一;它定义的宏可以出现在程序的任意位置;它定义之后的代码都可以使用这个宏。

#define 定义的宏常量可以直接使用,其本质为字面量。它与 const 定义的常量的区别是:const 修饰的常量本质是变量,占用内存;而字面量是不占用内存的。我们来看看下面这几个宏常量定义是否正确

#define ERROR -1
#define PATH1 "D:\test\test.c"
#define PATH2 D:\test\test.c
#define PATH3 D:\testtest.c

int main()
{
    int err = ERROR;
    char* p1 = PATH1;
    char* p2 = PATH2;
    char* p3 = PATH3;
}

我们分析下,前两个肯定正确,第三种猜测是正确的,因为宏定义只是简单的替换。第四种也是正确的,最后的 \ 我们看成是前面介绍的接续符。我们来单步编译下,看看结果

我们看到单步编译的时候,它没出错。就是进行简单的替换,我们再加上头文件,进行编译。结果如下

我们看到编译出错了,因为宏只是被预处理期处理,预处理期不会去检查语法,所以会单步编译通过。所以在编译检查语法的时候出错了。我们下来看个示例代码,代码如下

#include <stdio.h>

#define _SUM_(a, b) (a) + (b)
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
#define _DIM_(a) sizeof(a)/sizeof(*a)

int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};

    int s1 = _SUM_(a, b);
    int s2 = _SUM_(a, b) * _SUM_(a, b);
    int m = _MIN_(a++, b);
    int d = _DIM_(c);

    printf("s1 = %d\n", s1);
    printf("s2 = %d\n", s2);
    printf("m = %d\n", m);
    printf("d = %d\n", d);

    return 0;
}

我们分析,第14行返回相加和,因而第19行打印 3;第15行返回加和的平方,因而第20行打印 9;第16行返回最小值,因而第21行打印 1;第17行返回的是数组的个数,所以第22行打印 4。我们看看编译结果

结果跟我们分析的不太一样,中间两个不一样。我们来单步编译下,看看是什么样的

我们看到它的 main 函数是这样的,因而我们分析的是错的。那么在这块我们是忽略了宏表达式和函数的差异,那么宏表达式有哪些特性呢?如下:a> 宏表达式被预处理器处理,编译器不知道宏表达式的存在;b> 宏表达式用“实参”完全代替形参,不进行任何运算;c> 宏表达式没有任何的“调用”开销;d> 宏表达式中不能出现递归定义,如:#define _SUM_(n) ((n > 0) ? (_SUM_(n-1) + n) : 0); int s = _SUM_(5);

那么我们来思考下:宏定义的常量或表达式是否有作用域限制?我们来看看下面的代码

#include <stdio.h>

void def()
{
    #define PI 3.1415926
    #define AREA(r) r * r * PI
}

double area(double r)
{
    return AREA(r);
}

int main()
{
    double r = area(5);

    printf("PI = %f\n", PI);
    printf("d = 5; a = %f\n", r);
    
    return 0;
}

那么在 def 函数里定义的宏能否在 area 函数里使用呢?也就是说宏定义的作用域是否是具有函数作用域呢,我们来看看编译结果

它并没有报错,而是成功运行。在这里我们注释掉头文件和打印语句,我们来单步编译下,看看函数里是怎样的?

明显在 area 函数里直接展开宏,也就是说宏定义的常量没有作用域的限制。我们再来看看 C 语言中强大的内置宏

我们利用内置宏编写析下面的代码,代码如下:

#include <stdio.h>
#include <malloc.h>

#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)

#define FREE(p) (free(p), p=NULL)

#define LOG(s) printf("[%s] {%s:%d} %s \n", __DATE__, __FILE__, __LINE__, s)

#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END   }

int main()
{
    int x = 0;
    int* p = MALLOC(int, 5);
    
    LOG("Begin to run main code...");
    
    FOREACH(x, 5)
    BEGIN
        p[x] = x;
    END
    
    FOREACH(x, 5)
    BEGIN
        printf("%d\n", p[x]);
    END
    
    FREE(p);
    
    LOG("End");
    
    return 0;
}

我们在第4行定义 MALLOC 为申请堆空间并用指针来存储地址,第6行利用之前学习的逗号表达式来释放申请到的指针。第8行则是利用内置宏定义 LOG 打印信息,第10-12行分别定义 for 循环和{ }。我们来看看编译后打印的结果

我们看到内置宏是如此的强大,目前在 C 语言中是利用函数办不到来打印文件名和行数信息的。通过对宏定义的学习,总结如下:1、预处理期直接对宏进行文本替换,宏使用时的参数不会进行求值和运算;2、预处理期不会对宏定义进行语法检查,宏定义出现的缘分错误只能被编译器检测;3、宏定义的效率高于函数调用但会带来一定的副作用。后面我们会继续对 C 语言的学习。

欢迎大家一起来学习 C 语言,可以加我QQ:243343083。

原文地址:http://blog.51cto.com/12810168/2103317

时间: 2024-11-09 00:07:34

C之宏定义(十九)的相关文章

“全栈2019”Java第八十九章:接口中能定义内部类吗?

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第八十九章:接口中能定义内部类吗? 下一章 "全栈2019"Java第九十章:内部类可以向上或向下转型吗? 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复"Java学

初级篇第十一期:学习使用常量定义和宏定义

学习建议:自己动手,丰衣足食 学习周期:1周 学习目的:熟练使用常量定义和宏定义 学习答疑:欢迎来技术群里提问并做分享 学习工具:Xcode开发环境 学习内容:熟悉项目开发中常用的两个定义 我们一般定义常量数字和字符串的时候一般会考虑用常量来定义   static CGFloat const kDefaultColorLayerOpacity = 0.4f; 一般定义宏的时候,都是用来定义方法,用宏的时候一定要多注意使用哦,会关系到括号的问题   #define SWH_RGBA(r, g, b

《Linux内核设计与实现》读书笔记(十九)- 可移植性

摘自http://www.cnblogs.com/wang_yb/p/3512095.html <Linux内核设计与实现>读书笔记(十九)- 可移植性 linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个). 但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, Intel x86, MIPS和SPARC(虽然支持的还不是很完善). 从 v2.0版本开始加入了对 Motorala 68K和PowerPC

Python之路【第十九篇】:爬虫

Python之路[第十九篇]:爬虫 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本.另外一些不常使用的名字还有蚂蚁.自动索引.模拟程序或者蠕虫. Requests Python标准库中提供了:urllib.urllib2.httplib等模块以供Http请求,但是,它的 API 太渣了.它是为另一个时代.另一个互联网所创建的.它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务. import

宏定义的黑魔法 - 宏菜鸟起飞手册

转载:https://onevcat.com/2014/01/black-magic-in-macro/ 宏定义在C系开发中可以说占有举足轻重的作用.底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行.而在更高层级进行开发时,我们会将更多的重心放在业务逻辑上,似乎对宏的使用和依赖并不多.但是使用宏定义的好处是不言自明的,在节省工作量的同时,代码可读性大大增加.如果想成为一个能写出漂亮优雅代码的开发者,宏定义绝对是必不可少的技能(虽然宏本身

[翻译]NUnit---SetUp and SetUpFixture and Suite Attributes(十九)

SetUpAttribute (NUnit 2.0 / 2.5) 本特性用于TestFixture提供一个公共的功能集合,在呼叫每个测试方法之前执行.同时也用在SetUpFixture中,SetUpFixture在相同命名空间或者程序集也实现相同的作用. 在NUnit2.5之前,类必须只能有一个SetUp方法且必须是一个实例方法. 从NUnit2.5开始,SetUp方法可以使一个静态或者实例方法,而且在一个Fixture可以多次使用.通常多个Setup方法定义在不同层级的继承. 如果一个Setu

2017-1,福州第十九中学,信息学奥赛培训跟进表

(首先,请你收藏本页面) 联系教师:[email protected] 培训地点:十九中,一号楼,6层,第二机房 培训守则: 1.学员每次培训,均需携带如下物品:鞋套(禁止使用一次性鞋套).水笔.荧光记号笔.<奥赛一本通>(第四版). 2.定位就座,严肃学习纪律,严禁携带零食及饮料进入机房. 3.认真做好课前预习.课堂笔记.和课后复习作业的工作. 4.不迟到.不早退. 5.无故迟到.早退.缺课.不做作业--,次数较多的,自己回去跟家长说,不要再来浪费时间了. 6.所有的培训内容,以本页面的通知

NeHe OpenGL教程 第二十九课:Blt函数

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第二十九课:Blt函数 Blitter 函数: 类似于DirectDraw的blit函数,过时的技术,我们有实现了它.它非常的简单,就是把一块纹理贴到另一块纹理上. 这篇文章是有Andreas Lffler所写的,它写了一份原始的教

NeHe OpenGL教程 第三十九课:物理模拟

转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线教程的编写,以及yarn的翻译整理表示感谢. NeHe OpenGL第三十九课:物理模拟 物理模拟简介: 还记得高中的物理吧,直线运动,自由落体运动,弹簧.在这一课里,我们将创造这一切. 物理模拟介绍 如果你很熟悉物理规律,并且想实现它,这篇文章很适合你. 在这篇教程里,你会创建一个非常简单的物理引