C语言有参数宏定义与无参数宏定义

前两天上课,被JAVA老师问懵了,老师问:“你们学C语言,有没有写过带参的宏玩一玩”,说实话,我根本没听过什么带参的宏,我只用过宏定义,所以我下来一定要找个时间把这“带参的宏搞懂”,于是就有了这篇文章。

       C语言中宏定义分两种,无参的宏有参的宏

1.无参数的宏

       无参数宏定义的一般形式为:

#define name value//name是你起的名字,就跟起函数名一样,value是你要给这个名字赋予什么值
//示例:
#include <iostream>
using namespace std;
#define pi 3.14
int main()
{
        int r = 2;
        double s = pi*r*r;
        cout<<s;
}

       这种宏定义要求编译预处理程序将源程序随后所有宏名(注释与字符串常量除外)均用值替换。无参数的宏没什么好说的,但还是有些地方使用时要注意。

几点注意:

1. 在宏定义的#之前可以有若干个空格、制表符,但不允许有其它字符。宏定义在源程序中单独另起一行,换行符是宏定义的结束标志(不能在末尾加分号)。如果一个宏定义太长,一行不 够时,可采用续行的方法。续行是在键人回车符之前先键入符号"/"。注意回车要紧接在符号"/"之后,中间不能插入其它符号。

       2. 宏定义的有效范围称为宏定义名的辖域(也可以叫做生命周期,类似于变量的生命周期),辖域从宏定义的定义结束处开始到其所在的源程序文件末尾。宏定义名的辖域不受分程序结构的影响。可以用预处理命令#undef终止宏定义名的辖域。

3. 在新的宏定义中,可以使用前面已定义的宏名,示例:

# define R 2.5
# define PI 3.1415926
# define Circle 2*PI*R
# define Area PI*R*R

       4. 如有必要,宏名可被重复定义。被重复定义后,宏名原先的意义被新意义所代替。

2.有参数的宏

       有参数宏的定义形式一般为:

#define name(参数1,参数2,....) sentence//sentencen表示语句
//示例:
#define max(a,b) (a)>(b)?(a):(b)
#include <iostream>
using namespace std;
int main()
{
        int x = max(1+3,1+4);
        cout<<"x = "<<x;//x = 5
        return 0;
}

       注意!注意!注意!我这里为什么a和b要加括号?我换个示例,如果不加括号,看输出什么

#define pw(x) x*x
#include <iostream>
using namespace std;
int main()
{
        int x = pw(3+4);
        cout<<x;
        return 0;
}

       在我给出答案之前,或者我提醒你之前,你估计想不打这会输出什么,你可能会认为会输出49,但答案是22。为什么是22不是49?哪里错了?哪里都没错,他只不过依据了正常的加减乘除顺序而已,因为你没加括号,所以他不会将3+4作为一个整体来进行乘法运算,而是这个样子3+4*3+4,先乘除后加减,你说这等于多少?所以在进行宏定义的时候,多加几个括号,总没问题。

       带参的宏,类似与函数,看下面的程序,输出我给了,读者可以先分析

#include <iostream>
using namespace std;
#define swap1(a,b) t=a;a=b;b=t;
int swap2(int c,int d)
{
        int t;
        t = c;
        c = d;
        d = t;
}
int main()
{
    int a,b,c,d,t;
    a = 5;
    b = 3;
    c = 5;
    d = 3;
    swap1(a,b);
        swap2(c,d);
        cout<<a<<" "<<b<<endl;//3 5
        cout<<c<<" "<<d;//5 3
    return 0;
}

       你会发现函数,并没有交换实参,而宏交换了,但是如果把函数中的参数改为指针或者引用就能成功交换了。下面给出带参的宏和函数的区别:

       1. 宏会在编译器在对源代码进行编译的时候进行简单替换,不会进行任何逻辑检测,即简单代码复制而已。        2. 宏进行定义时不会考虑参数的类型。        3. 参数宏的使用会使具有同一作用的代码块在目标文件中存在多个副本,即会增长目标文件的大小。        4. 参数宏的运行速度会比函数快,因为不需要参数压栈/出栈操作。        5. 函数只在目标文件中存在一处,比较节省程序空间。        6. 函数的调用会牵扯到参数的传递,压栈/出栈操作,速度相对较慢。        7. 函数的参数存在传值和传地址(指针)的问题,参数宏不存在。

       3. 宏中”#”和”##”的用法

       一般用法:

1.使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起,看下面的示例:

#include<cstdio>
#include<climits>
using namespace std;
#define STR(s) #s
#define CONS(a,b) int(a##e##b)
int main()
{
        printf(STR(vck)"\n");           //输出字符串"vck"
        printf("%d", CONS(2,3)); // 2e3 输出:2000
        return 0;
}

2.当宏参数是另一个宏的时候,需要注意凡是宏定义里有用''#''或''##''的地方宏参数是不会再展开,看示例:

//1.非"#"和"##"的情况 
#include<cstdio> 
#include<climits> 
using namespace std;
#define TOW (2) 
#define MUL(a,b) (a*b)
int main() 
{ 
        printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
}

//2.当有''#''或''##''的时候  #include<cstdio> #include<climits> using namespace std; #define A (2) #define STR(s) #s #define CONS(a,b) int(a##e##b) int main() {         printf("int max: %s\n", INT_MAX);//INT_MAX         printf("%s\n", CONS(A, A));//compile error }

       这个时候,INT_MAX和A都不会在被展开,解决这个问题的方法很简单,多加一层转换宏,加这层宏的用意是把所有宏的参数在中间曾全部展开。看下面的示例

//2.当有"#"或"##"的时候 
#include <bits/stdc++.h>
using namespace std;
#define _STR(s) #s
#define STR(s) _STR(s) // 转换宏
int main()
{
        printf("int max: %s\n",STR(INT_MAX));//int max: 2147483647
        return 0;
}

感谢阅读

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏向治洪

迭代子模式

概述 概念:在阎宏博士的《JAVA与模式》中关于迭代子模式的定义是这样的:迭代子模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个...

1777
来自专栏张善友的专栏

SQL Express - Client Synchronization Sample

Synchronization Services for ADO.NET 是微软推出的同步框架( Microsoft Synchronization Frame...

1889
来自专栏阿杜的世界

MySQL文档阅读(一)-数字类型

MySQL支持很多系列的SQL数据类型:数字类型(numeric types)、日期和时间类型(date and time types)、字符串类型(字符和字节...

341
来自专栏向治洪

迭代子模式

概述 概念:在阎宏博士的《JAVA与模式》中关于迭代子模式的定义是这样的:迭代子模式又叫游标(Cursor)模式,是对象的行为模式。迭代子模式可以顺序地访问一个...

2016
来自专栏猛牛哥的博客

aardio在外部进程执行汇编代码时传入自定义参数的方法

1704
来自专栏全华班

重新认识你认识的Hibernate(二)

Hibernate估计大家已经用过很多年了吧,好多同学说用过Hibernate,不需要你来讲,但再仔细想想,你能告诉我Hibernate是什么吗? 今天带大家重...

3674
来自专栏菩提树下的杨过

[转自JeffreyZhao]在LINQ to SQL中使用Translate方法以及修改查询用SQL

目前LINQ to SQL的资料不多——老赵的意思是,目前能找到的资料都难以摆脱“官方用法”的“阴影”。LINQ to SQL最权威的资料自然是MSDN,但是M...

1915
来自专栏YG小书屋

ES 深度分页scroll使用方式

1752
来自专栏calmound

多线程-互斥变量

第一个 CreateMutex 函数功能:创建互斥量(注意与事件Event的创建函数对比) 函数原型: HANDLE  CreateMutex(   LPSEC...

2434
来自专栏.Net移动开发

.Net语言 APP开发平台——Smobiler学习日志:如何在手机上实现表单设计

其中包括Height属性(列标题高度)、FontSize属性(列标题文本大小)、BackColor属性(列标题背景颜色)和ForeColor属性(列标题文本颜色...

801

扫码关注云+社区