目录
函数和数据类型
函数式宏
函数和函数式宏
函数式宏和对象式宏
不带参数的函数式宏
函数式宏和逗号运算符
函数式宏和函数类似并且比函数更加灵活,下面我们就来学习函数式宏的相关内容。
函数和数据类型
我们来编写一个程序,它能计算出所读取数值的平方,并将结果显示出来,我们先来编写适用于int类型和double类型的函数。
/*整数和浮点数的平方*/
#include<stdio.h>/*计算int型数的平方值*/
int sqr_int(int x)
{return x * x;
}
/*计算double型数的平方值*/
double sqr_double(double x)
{return x * x;
}
int mian()
{int x;double n;printf("请输入一个整数:");scanf("%d", &x);printf("该整数的平方是%d\n", sqr_int(x));printf("请输入一个整数:");scanf("%lf", &n);printf("该整数的平方是%f\n", sqr_double(n));return 0;
}
如果我们又想计算其他数据类型的平方呢?比如计算long型数的平方,就得创建出一个sqr_long的函数,如果接二连三的写出这种功能相近,名称相似的函数,程序就会充斥着这种似是而非的函数
下面我们来学习解决办法:函数式宏
函数式宏
函数式宏(function—like macro)较之对象式宏可以进行更为复杂的代换。
#include<stdio.h>#define sqr(x) ((x) * (x))//计算x平方的函数式宏int main()
{int x;double n;printf("请输入一个整数:");scanf("%d", &x);printf("该整数的平方是%d\n", sqr(x));printf("请输入一个整数:");scanf("%lf", &n);printf("该整数的平方是%f\n", sqr(n));return 0;
}
//#define 给出的命令如下:
下文中若出现sqr(☺)形式的表达式就将其展开为:
((☺)* (☺))
因此,在调用printf函数时就可以像下面一样展开并执行:
printf("该数的平方是%d\n", ((x) * (x)));
函数和函数式宏
函数和函数式宏的调用看上去相同,但也有以下几个区别:
■函数式宏sqr是在编译时展开并填入程序的,因此只要能使用双目运算符 * 进行乘法运算的数据类型,都能使用函数式宏。
■而函数定义则需要每个形参都定义各自的数据类型,返回值类型也都只有一种,就这点而言,函数较为严格。
■函数为我们默默无闻的进行一系列的复杂处理:
☞参数传递(将实参复制给形参)
☞函数调用和函数的返回操作(函数流程的控制)
☞返回值的传递
而函数式宏所做的工作只是宏展开和填入程序,并不执行上述步骤。
■根据以上特征,函数式宏或许能使程序的运行速度稍微提高,但是程序自身可能会变得臃肿(如果宏展开式极为复杂,那么在使用到它的所有地方都会填入这些复杂的表达式)。
■函数式宏在使用时必须小心,比如sqr((a++)* (a++)),每次展开a的值都会递增两次。在不经意间表达式被执行了两次,导致程序出现了意料之外的结果,我们称这种情况为宏的副作用。
注意:在定义和使用函数式宏的时候,要仔细考虑是否会使用产生副作用。
☞将函数版的sqr_int作为sqr_int(a++)调用时,a的值不会调用两次,如果是宏版,则要将sqr(a)和a++分开。
函数式宏和对象式宏
如果在宏名称sqr和紧邻其后的( 之间插入空格,进行如下宏定义:
#define sqr (x) ((x) * (x))
则sqr会被编译器当做对象式宏,程序中的sqr都会被替换为(x) ((x) * (x))
因此我们在定义函数式宏时,不要在宏名称和(之间插入空格。
以下是计算二值之和的函数式宏:
#define sum_of(x,y) x + y
我们使用以下语句来调用这个函数式宏
z = sum_of(a, b) * sum_of(c, d);
让我们来看看宏展开式是否符合我们的意愿呢?
z = a + b * c + d;
很显然结果不尽人意,保险起见我们在宏定义时将每个参数以及整个表达式都用括号括起来
#define sum_of(x,y) ((x) + (y))
这样表达式就能正确展开了:
z = (a + b) * (c + d);
不带参数的函数式宏
函数式宏也可以像函数那样进行不带参数的定义,例如下面这个响铃的宏alert()
#define alert() (putchar('\n'))
函数式宏和逗号运算符
下面我们来介绍函数式宏的一个重要使用方法,我们先来看下错误示范:
#include<stdio.h>#define puts_alert(str) {putchar('\a'); puts(str)}int main()
{int n;printf("请输入一个整数:");scanf("%d", &n);if(n)puts_alert("这个整数不是0");elseputs_alert("这个整数是0");return 0;
}//本程序在编译时报错,因此不能运行
让我们来分析下原因:
函数式宏put_alert的定义是在puts函数显示字符串str时响铃,只不过这个程序在编译时出错,不能运行。
main函数的if语句展开后如下图所示,if语句会在第一个复合语句{ }处结束,这时因为末尾的 ;会被视为空语句,因此编译器会认为“没有if,为何出现了else”(即使这样,也不能去掉{}否则会出现别的错误)
下面就需要讲到逗号运算符了:
a,b 按顺序判断a和b,整个表达式最终生成b的判断结果 |
#include<stdio.h>#define puts_alert(str) (putchar('\a'), puts(str))int main()
{int n;printf("请输入一个整数:");scanf("%d", &n);if(n)puts_alert("这个整数不是0");elseputs_alert("这个整数是0");return 0;
}
一般由逗号运算符连接的两个表达式“a, b”在语法上可以视为一个表达式(其实不仅限于逗号运算符,只要是由运算符连接的多个表达式,例如“a + b”,都可以视为一个表达式),因此在本程序中if语句在语法上就是正确的。
如果宏定义中要代换两个以上的表达式,则使用逗号运算符连接,使其在语法上构成一个表达式。
我们对逗号运算符是怎么执行的来具体说明下:
对于逗号运算符“a, b”,会按顺序判断表达式a和b。对左侧的a仅进行判断,判断结果会被省略去,对于右侧的表达式b进行判断所得到的类型和值,就是逗号表达式“a, b”的类型和值。
例如:i=1,j=5
运行x = (++i, ++j),则i和j的值都会递增,但是递增后j的值会被赋值给x。
感觉基本数据类型中的整型和字符型、浮点型的知识点好复杂啊!!!