在数学中我们用过sin和ln这样的函数,例如sin(π/2)=1,ln1=0等等,在C语言中也可以使用这些函数:
例 3.1. 在C语言中使用数学函数
#include <math.h> #include <stdio.h> int main(void) { double pi = 3.1416; printf("sin(pi/2)=%f\nln1=%f\n", sin(pi/2), log(1.0)); return 0; }
编译运行这个程序,结果如下:
$ gcc main.c -lm $ ./a.out sin(pi/2)=1.000000 ln1=0.000000
在数学中使用函数有时候书写可以省略括号,而C语言要求一定要加上括号,例如sin(pi/2)
这种形式。在C语言的术语中,pi/2
是参数(Argument),sin
是函数(Function),sin(pi/2)
是函数调用(Function Call)。这个函数调用在我们的printf
语句中处于什么位置呢?通过“表达式”一节的学习我们知道,这应该是放表达式的位置。因此,函数调用也是一种表达式。这个表达式由函数调用运算符(也就是括号)和两个操作数组成,操作数sin
称为Function Designator,是函数类型(Function Type)的,操作数pi/2
是double
型的。这个表达式的值就是sin(pi/2)
的计算结果,在C语言的术语中称为函数的返回值(Return Value)。
现在我们可以完全理解printf
语句了:原来printf
也是一个函数,上例的printf
语句中有三个参数,第一个参数是格式化字符串,是字符串类型的,第二个和第三个参数是要打印的值,是浮点型的,整个printf
就是一个函数调用,也就是一个表达式,因此printf
语句也是表达式语句的一种。由于表达式可以传给printf
做参数,而sin(pi/2)
这个函数调用就是一个表达式,所以根据组合规则,我们可以把sin
调用套在printf
调用里面,同理log
调用也是如此。但是printf
感觉不像一个数学函数,为什么呢?因为像sin
这种函数,我们传进去一个参数会得到一个返回值,我们用sin
函数就是为了用它的返回值,至于printf
,我们并不关心返回值(事实上它也有返回值,表示实际打印的字符数),我们用printf
不是为了用它的返回值,而是为了利用它所产生的副作用(Side Effect)--打印。C语言的函数可以有Side Effect,这一点是它和数学函数在概念上的根本区别。
Side Effect这个概念也适用于运算符组成的表达式。比如a + b
这个表达式也可以看成一个函数调用,运算符+
是一个函数,它的两个参数是a
和b
,返回值是两个参数的和,传入两个参数,得到一个返回值,并没有产生任何Side Effect。而赋值运算符是产生Side Effect的,如果把a = b
这个表达式看成函数调用,传入两个参数a
和b
分别做左值和右值使用,返回值就是所赋的值,既是b
的值也是a
的值,但除此之外还产生了Side Effect,就是a
的值被改变了,改变计算机存储单元里的数据或者做输入或输出操作,这些都算Side Effect。
回想一下我们的学习过程,一开始我们说赋值是一种语句,后来学了表达式,我们说赋值语句是表达式语句的一种,一开始我们说printf
是一种语句,现在学了函数,我们又说printf
也是表达式语句的一种。随着我们一步步的学习,把原来看似不同类型的语句统一成一种语句了。学习的过程总是这样,初学者一开始接触的很多概念从严格意义上说是错的,但是很容易理解,随着一步步学习,在理解原有概念的基础上不断纠正,不断泛化(Generalize)。比如上一年级老师说,小数不能减大数,其实这个概念是错的,后来引入了负数就可以减了,后来接触了分数,原来的正数和负数的概念就泛化为和分数相对的整数,上初中学了无理数,原来的整数和分数的概念就泛化为有理数,再上高中学了复数,有理数和无理数的概念就泛化为实数。坦白说,到目前为止本书的很多说法是不完全正确的,但这是学习理解的必经阶段,到后面的章节都会逐步纠正的。
现在也可以详细解释程序第一行#号(Pound Sign,Number Sign或Hash Sign)后面加个include
的确切含义了,它后面写在尖括号(Angel Bracket)中的是一个文件名,称为头文件(Header File),其中描述了我们程序中使用的系统函数,因此要使用printf
就必须包含stdio.h
,要使用数学函数就必须包含math.h
,如果什么系统函数都不用就不必包含任何头文件,例如写一个程序int main(void){int a;a=2;return 0;}
,不需要包含头文件可以编译通过,当然这个程序什么也做不了。
使用math.h
中的函数还有一点特殊之处,gcc
命令行必须加-lm
选项,因为数学函数位于libm.so
库文件中(通常在/lib
目录下),-lm
选项告诉编译器,我们程序中用到的数学函数要到这个库文件里找。本书用到的大部分库函数(例如printf
)位于libc.so
库文件中,以后称为libc
,使用libc
中的库函数在编译时不需要加-lc
选项,当然加了也不算错,因为这个选项是gcc
默认的。关于头文件和函数库目前理解这么多就可以了,以后再详细解释。