Linux C 编程学习第二天2

数学函数

在C语言中可以使用像是 sin 和 ln 这样的函数,需要引入一个头文件,#include <math.h>

在C语言中,使用数学函数一定要加上括号,例如: log(1.0)   在这个式子里,1.0是参数,log是函数,可以直接通过 printf 输出,还记的前面说的吗,直接使用printf输出一定要注意数据的类型,否则会造成数据丢失

printf("ln 1 = %f", log(1.0));

对于上式  log(1.0)  是函数调用,函数调用也是一种表达式,C语言中函数调用表达式的值称为函数的返回值(Return Value)

实际上 printf() 也是一个函数,但是我们不必在意它的返回值,我们调用printf函数是为了打印,这称之为副作用(Side Effect)  C语言的函数可以有副作用,这是和数学函数不一样的地方

改变计算机存储单元里的数据或者做输入输出操作都算 Side Effect

引入一下原作的一段话,我觉得说的很有意思:

 

泛化 (Generalize) 是一个很有味道的词

在Ubuntu中执行gcc命令有一点需要注意,当使用了 math.h 库的时候,gcc 命令必须加上 -lm

因为数学函数位于libm.so库文件中。

虽然 printf 函数是位于 libc.so 库中的,但使用 libc.so 库函数在编译的时候不需要加 -lc 选项,加了也不算错,因为这个选项是gcc的默认选项,剩下的问题在后面会详细学到。

 

 

自定义函数

我们将要开始自定义函数,在一开始学习C语言的时候,我们其实就定义了一个函数,main 函数,我们在main函数中输出了 hello world  

函数的命名也要遵循前面的标识符命名规则

由于大学里各种教材教的各种写法,我们先明白几个问题

为什么有的书上写的是 mian()   有的是 int main()   还有的是 int main(void)  有的在main函数中加入了return 0 ,有的没有return?

由于我们定义的main 函数不带任何参数,所以参数列表应该写成 void ,main 的返回值是 int 型的,所以 return 0 表示返回值为0,为什么要返回 0 呢,因为 main 函数是被操作系统调用的,通常程序执行成功就返回 0 ,在执行过程中出错就返回一个非零值,我们可以试试在最后将return 0 ; 改成 return 2 ;  重新编译看看会出现什么现象。

上面的问题并没有结束,main() 这种写法,不写返回值也不写参数列表,这是Old Style C的风格,因为Old Style C 规定不写返回值就表示返回 int ,不写参数就表示参数类型和个数没有明确指出,这种宽松的规定看似方便,但是会倒是编译器无法检查出程序的bug ,虽然 C 标准为了兼容问题依旧保留了这样的作法,但是作为程序员不应该使用这样的写法。

在系统调用 main 函数的时候是传递参数的,实际上int main()  这并不是最标准的写法,最最最标准的写法应该是 int main(int argc, char *argv[])      C语言也允许 int main(void)  这种写法,除了这两种写法外,定义main函数的其他写法都是错误,或不可移植的。

有时候我们定义函数并不是为了该函数返回某个值,我们经常会为了Side Effect 来定义函数,可以来举个例子:

void re_zhu(void)

{

  printf("这篇日记是re_zhu写的");

}

当我们调用re_zhu()  这个函数的时候,它就会输出      这篇日记是re_zhu写的     这样的语句

同一个函数可以多次调用,可以相互调用,也可以镶嵌调用,你可与用函数1去调用函数2,而函数2又是调用函数3,至于函数1调用函数2,函数2调用函数3,函数3去调用函数1 这样的死循环写法,我想在计算机还没崩溃前,赶快 Ctrl + c 吧。

就像是shell一样,你可以把很多命令(函数或者操作)整合到一个函数里,这样可以使代码更加简洁,比如我想打印三遍    这篇日记是re_zhu写的     完全没必要调用三次 re_zhu();

可以写一个这样的函数:

void rere_zhu(int i)

{

  while( i - - )

  re_zhu();

}

这样你想打印3次,就可以直接使用 rere_zhu(3);  就可以了,这样的操作可以使代码更加整洁和好理解。(PS:原谅我的函数命名这么没有品味)

书中讲到读代码的顺序和读文章不一样,有时不是从上往下读并不一定最优解,比如我上面举的例子,按照我的编程习惯,我会将 re_zhu() 函数和 rere_zhu() 函数写在 main() 函数前面,那么按照从上往下读当然使可以的,但是如果我写了很多的自定义函数在 main() 前面,我相信你是没有耐心一个一个往下看的,或者说即使你看完了那些自定义函数,到了main()函数中,就已经记不清那些函数了,这个时候就应该换个方式,直接看 main() 函数,main() 函数调用了上面的某个函数,你在去看那个函数,如果该函数还调用了其他函数,继续跟进,直到你明白了 main() 中的这段代码是什么意思。

读代码的过程就是在模拟计算机执行的过程,尤其是在使用单片机的时候,这种思想更重要,如果你玩过单片机的话,应该能深刻体会这点。

 

好的,我们现在看一下函数声明,定义,原型,这几个概念

如果你见过 void rere_zhu(int i);     这种写法叫做函数声明,不是函数定义,只有带函数体的声明才叫定义,函数也是和变量一样的,编译器只有见到函数定义才会生成指令,函数声明为编译器提供了有用的信息,编译器在翻译代码的过程中,只有见到函数原型之后才知道函数的名字,参数和返回值,然后碰到函数调用才直到如何生成对应指令,所以函数原型必须出现在调用前,但是还有一种方法,在主函数前我使用  void rere_zhu(int i);   void re_zhu(void);    提前声明了这两个函数,而把他们的函数体放在main()函数后,这样也是可行的。

但是!!我们明白这点并不是要按照这样的风格去写代码,了解这些是为了读懂、维护使用这样风格代码,没错,这个风格就是Old Style C ,我们不应该按照这样的方式去写代码,如果把按照上面风格的代码前面的声明去掉,编译器会警告,但是编译依旧能通过,这称之为函数的隐式声明 Implicit Declaration  隐式声明的函数返回值类型都是int,对于上述函数并没有返回值,所以是void,好了,main() 在运行时到了这个函数,编译器认为返回是 int ,但是我们函数的原型是返回 void ,这就是警告的原因,但是我们的函数并没有用到返回值,用的是副作用,所以执行结果是对的,如果我们的函数用到了返回值,那么将会出现错误,所以我们要极力避免使用这样的写法。

形参和实参

上面我们看见了,函数中是可以有参数的,需要注意的是,定义变量可以把同类型的变量列在一起,但是在定义参数的时候,不可以这样,必须逐个列出

例如:   re_zhu(int i, int j, float k)          如果写成 re_zhu(int i, j, float k)  就是错误的,切记

 

 使用  man 3 printf   可以查看 printf 函数的原型

 这个位置之后用实际的图来补充

printf 也是函数,但是它的参数可以很多,也可以很少,这些参数是不确定的,这称为可变参数,之后会讨论,按理说每个函数都应该明确规定返回值和参数类型和个数,但是实际上,像printf()这样的规定也是一种明确的规定,调用函数时也要严格遵守这些规定。

有时我们把函数叫做接口,调用函数就是使用这个接口,使用接口的前提是必须和接口保持一致。

 

前面有个关于 man 3 printf   这样的命令

我们来具体看看这个:

 

 

 

posted @ 2021-04-29 01:57  哿与银冰  阅读(108)  评论(0)    收藏  举报