函数
函数分为库函数和自定义函数,⾃定义函数和库函数是⼀样的,形式如下:
- ret_type 是函数返回类型
- fun_name 是函数名
- 括号中放的是形式参数
- {}括起来的是函数体
下面我们通过一张图来加深对函数的理解:
如上图所示,我们可以把函数想象成⼩型的⼀个加⼯⼚,⼯⼚得输⼊原材料,经过⼯⼚加⼯才能⽣产出产品,那函数也是⼀样的,函数⼀般会输⼊⼀些值(可以是0个,也可以是多个),经过函数内的计算,得出结果。
- ret_type 是⽤来表⽰函数计算结果的类型,有时候返回类型可以是 void ,表⽰什么都不返回
- fun_name 是为了⽅便使⽤函数;就像⼈的名字⼀样,有了名字⽅便称呼,函数有了名字⽅便调⽤,所以函数名尽量要根据函数的功能起的有意义。
- 函数的参数就相当于,⼯⼚中送进去的原材料,函数的参数也可以是 void ,明确表⽰函数没有参数。如果有参数,要交代清楚参数的类型和名字,以及参数个数。
- {}括起来的部分被称为函数体,函数体就是完成计算的过程。
举个例⼦:
写⼀个加法函数,完成2个整型变量的加法操作。
输出结果为:
函数的参数部分需要交代清楚:参数个数,每个参数的类型是啥,形参的名字叫啥。
上⾯只是⼀个例⼦,未来我们是根据实际需要来设计函数,函数名、参数、返回类型都是可以灵活变化的。
#define定义宏
#define包含两种方式,#define定义常量和#define定义宏。#define定义常量很简单,就是用一个符号来给常量命名,在预处理阶段会对常量进行替换,由于我们这里要讲的是#define定义宏,所以对于#define定义常量,这里我们不过多赘述。
和#define定义常量类似,#deifne定义宏类似于用一个符号来代替函数,我们只需要进行简单传参即可。
#define 机制包括了⼀个规定,允许把参数替换到⽂本中,这种实现通常称为宏(macro)或定义宏 (define macro)。
下⾯是宏的申明⽅式:
其中的 parament-list 是⼀个由逗号隔开的符号表,它们可能出现在stuff中。
注意:参数列表的左括号必须与name紧邻,如果两者之间有任何空⽩存在,参数列表就会被解释为stuff的 ⼀部分。
通过上面的介绍,或许你对宏还是不太理解,下面我们通过一个例子,来更好理解宏。
由于上面函数我们定义的是加法函数,同样,我们也定义一个能实现加法的宏:
可以看到,这个宏接收两个参数,分别为 x和y,如果在上述声明之后,你把Add(a,b); 置于程序中,预处理器就会⽤下⾯这个表达式替换上⾯的表达式: a+b
输出结果为:
当然,由于宏只是进行简单替换,所以要是宏定义不够严谨,可能会发生不可预料的错误,下面我们通过一个例子来证明:
原来例子的输出结果为30,如果我们想要输出结果为300,那在使用宏前面乘上一个10,是否可以得到我们想要的结果呢?如图:
可以看到,输出结果竟然是120,不符合我们预料的结果,那么为什么会这样呢?
原因就是,宏定义只是在预处理阶段进行简单的替换,而不会像函数那样,先计算出表达式的结果,再把结果传给形参,这也是函数和宏的区别之一,我们可以把上面宏的使用替换成表达式,可以更直观地理解宏:
如注释部分所示,由于*号的优先级比+号高,所以先计算10*a,再+b,所以结果就为120。
那么有没有什么办法能解决呢?答案是肯定的,就是要在宏定义中适当使用括号,如图:
这样,由于括号优先级最高,所以优先计算括号内的表达式,再计算括号外面的表达式,这样就能得到我们想要的结果:
注意:所以⽤于对数值表达式进⾏求值的宏定义都应该⽤这种⽅式加上括号,避免在使⽤宏时由于参数中的操作符或邻近操作符之间不可预料的相互作⽤。
宏和函数的对比
宏通常被应⽤于执⾏简单的运算。
⽐如在两个数中找出较⼤的⼀个时,写成下⾯的宏,更有优势⼀些。
那为什么不⽤函数来完成这个任务?
- ⽤于调⽤函数和从函数返回的代码可能⽐实际执⾏这个⼩型计算⼯作所需要的时间更多。所以宏⽐函数在程序的规模和速度⽅⾯更胜⼀筹。
- 函数的参数必须声明为特定的类型。所以函数只能在类型合适的表达式上使⽤。反之 这个宏怎可以适⽤于整形、⻓整型、浮点型等可以⽤于 > 来⽐较的类型。宏的参数与类型⽆关。
和函数相⽐宏的劣势:
- 每次使⽤宏的时候,⼀份宏定义的代码将插⼊到程序中。除⾮宏⽐较短,否则可能⼤幅度增加程序的⻓度。
- 宏是没法调试的。
- 宏由于类型⽆关,也就不够严谨。
- 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。⽐如:宏的参数可以出现类型,但是函数做不到。
可以看到,我们在动态分配内存时,将内存的类型和个数都作为参数传递给宏,而在函数的参数中,是不能出现类型的。因此,该方法在函数中不可用。
宏和函数的⼀个对⽐:
属性 | #define定义宏 | 函数 |
代码长度 | 每次使用时,宏代码会被插入到程序中,除了非常小的宏之外,程序的长度会大幅增长。 | 函数的代码只出现在一个地方,每次调用函数时,都调用那个地方的同一份函数。 |
执行速度 | 更快 | 存在函数的调用和返回的额外开销,执行速度会慢一些。 |
操作符优先级 | 宏参数的求值是在所有周围表达式的上下文环境里,除非加上括号,否则邻近操作符的优先级可能会产生不可预料的后果,所以建议宏在书写的时候多写括号。 | 函数参数只在函数调用的时候求值,结果值传递给函数。表达式的值是可以预测的。 |
带有副作用的参数 | 参数可能被替换到宏体中的多个位置,如果宏的参数被多次计算,带有副作用的参数求值可能会产生不可预料的结果。 | 函数参数只在传参的时候求值,易控制。 |
参数类型 | 宏的参数与类型无关,只要对参数的操作是合法的,它就可以使用于任何参数类型。 | 函数的参数是与类型有关的,如不同,就需要不同的函数,即使任务是相同的。 |
调试 | 宏是不方便调试的 | 函数是可以逐语句调试的 |
递归 | 宏是不能递归的 | 函数是可以递归的 |