3.函数
函数分为两大类:标准库函数和自定义函数
标准库函数即我们所说的stdio库中的输入输出函数,math库中的幂函数pow,sqrt,绝对值函数fabs等等
自定义函数则是由我们自己编写的一个函数,如一个求圆面积函数(computArea),写在main函数之外,只要在main函数里面接收这个函数返回的结果并输出即可,或者在computArea函数里面直接输出也是可以的.
(1)函数的定义与调用
下面介绍定义函数基础的语法
函数类型 函数名(形式参数表){
函数体
}
解析:函数有函数头和函数体两部分组成,函数头由函数类型,函数名和形式参数表(形参)组成.
函数类型是值函数返回值的类型,即指return后跟的也要是什么类型的数据,两者要相同,即如果是int类型的函数,return后跟的要是个整型数据.
如果函数没有返回值,那么它的类型就应该指定为void类型,即空类型.
函数名必须是一个有效的标识符,即一个能让人看的懂的,让别人知道什么作用的合规名字.这里的就是计算面积的意思
形式参数是用逗号分割的变量声明表,这些变量称为函数的形式参数,以后简称形参,形参用于就收传给这个函数的数据,如下列代码中,
double computeArea(double r){ }
r这个形参就相当于一个别名,它的数据是main函数中的radius传来的,目的是让这个radius的数据能在computeArea函数中延续下去.
下面让我们用一个计算圆面积的例子来做一个标准规范c
#include <stdio.h> #include <math.h> #define PI 3.14159 double computeArea(double r){ //函数类型 函数名(形式参数) ----函数头 //以下3段为函数体部分 double area; area = PI * r * r; return area;//返回的area的类型是double与函数类型相同 } int main(){ double radius; scanf("%lf",&radius); if(radius >= 0){//进行判断 printf("圆面积:%.2f",computeArea(radius));//这括号里的radius就是实参 .2f限制只输出2为小数,默认输出6位小数 } else{ printf("半径不合法"); } }
示例1:
2
圆面积:12.57
示例2:
-1
半径不合法
解析:
1.首先,编译器会首先执行main函数,用于启动整个程序。函数其实是遵循着"先定义,后调用"的思想概念的,因为main函数要调用这个函数,就会从第 一行代码开始查找此函数(computeArea),所以要在main函数之前先定义(或者函数声明,下面会说)好了,如果computeArea函数放在main函数之后,main就找不到此函数 了。
2.执行到printf("圆面积:%.2f",computeArea(radius)); 这一行时,程序会去寻找->computeArea函数,找到了,由实参(radius)传入的值也会赋值给形参(r),再 往下执行代码,当执行到 return area; 时,就会把算好的面积传给调用此函数的地方,即 printf("圆面积:%.2f",computeArea(radius)); 这一行代码, computeArea(radius)这个函数此时就成为了传回来的面积值.输出之后,程序结束。
(2)void函数说明:
如果一个函数没有返回值,那么这个函数的类型就为void,那么是什么时候要用到void函数呢,其实这要看你需要什么吧,比如上面的computeArea函数, 也可以是void类型的,即在main函数里面直接调用它,把实参传入,再在computeArea函数里面判断,再直接输出即可。
#include <stdio.h> #include <math.h> #define PI 3.14159 void computeArea(double r){//注意函数类型最好要是void,如果是其他类型虽然不会报错,但并不符合我们的编程习惯,如果没有返回值则一般都为void //函数类型 函数名(形式参数) ----函数头 //以下3段为函数体部分 double area; if(r >= 0){//进行判断 area = PI * r * r; printf("圆面积:%.2f",area);//直接输出 } else{ printf("半径不合法"); } } int main(){ double radius; scanf("%lf",&radius); computeArea(radius);//将实参传入,再调用函数 //如果函数类型为void,函数调用通常都当一条语句来写,如上面这条代码. return 0;//表示程序结束,也可以不加 }
(3)谓词函数和函数声明说明:
有一类比较特殊的函数,其返回值为布尔类型(bool),我们编程习惯上,一般将谓词函数以is开头.
典型例子:输入一个回文数(即正读和反读都是一样的数,如515,262,7667等等),要求你调用一个函数判断是否为回文数,如果是则返回1,否则返回0.
如果弄不懂,拿笔按程序思路一算,就能出来,其实还有很多种其他方法,这里就不一一列举了.
#include <Stdio.h> int isPalindrome(int num);//这里并没有函数体,只是一个函数声明 //函数声明就是告诉编译器,这里有一个xxx函数,如果编译器调用的就是这个函数,函数声明会把它引到函数定义的地方,即真正写了函数体的地方 int main() { int n; scanf("%d",&n); if(isPalindrome(n)){//if语句说过,这样子这一条语句就等同于isPalindrome(int num)!=0,那么返回的就是1,就是回文数 printf("%d是回文数",n); } else{ printf("%d不是回文数",n); } } int isPalindrome(int num){ int sameNum = num;//因为 num进行了取整操作,即每次去掉末尾那个数,如675,675/10 = 67 int isPnum = 0;//声明一个变量来判断输入的数字是否回文数,来进行判断 while(num!=0){//进行逆序操作:845逆序是548,646逆序是646,121逆序是121,所以看出回文数就是逆序之后和原数相同的数 isPnum = isPnum * 10 + num % 10;//每次取它个位数,再将他取出的个位数每次乘10,变成更高一位的数,再存到isPnum中 num/=10; } if(isPnum == sameNum){//拿逆序之后的数字与原数字进行判断,如果相同,那么是回文数 return 1; } else{ return 0; } }
(4)递归函数
递归的一个典型就是求一个数的阶乘,递归一般分为两个部分:递推和回归。
我们都知道,n!=n (n-1)!,而(n-1)!又等于n-1 (n-2)!,如此下去,但是一个阶乘总有到头的时候,即到了0!= 1这一步,这一系列的操作就是递推了,0!= 1我们称为基本情况,当递推碰到基本情况时,就开始回归了。
我们知道了0!= 1了,那就再回推算,通过0!算出1!( 1!= 1 * (1-1)!),再由1!算出2!,直到通过(n-2)!算出 (n-1)!,再由(n-1)!算出n!,这个过程就称为回归.
#include <Stdio.h> double factorial(double num);//这里并没有函数体,只是一个函数声明 //函数声明就是告诉编译器,这里有一个xxx函数,如果编译器调用的就是这个函数,函数声明会把它引到函数定义的地方,即真正写了函数体的地方 int main() { double n; scanf("%lf",&n); printf("%.0f的阶乘是:%.0f",n,factorial(n)); } double factorial(double num){ if(num == 0){//当num为0时,即到了基本情况,0!= 1,返回即可 return 1; } else{ return num*factorial(num-1);//n*(n-1)!,n-1进入了factorial函数,当成了num,判断是不是到0了,没到0,就继续 } }
解析:有可能会问,为什么要输入一个双精度浮点进去,阶乘不都是整数吗?对,但是因为程序精度问题,整型表达上限最大才21亿左右,而double能大很多,所以进行一个输出处理,看起来输出和整型没声明区别。
(5)变量的作用域
(1)局部变量和全局变量
局部变量:即在一对花括号内(复合语句)或函数内部声明的变量成为局部变量(也称内部变量)。
全局变量:默认值为0,在函数外部声明的变量,它可以在多个函数中使用,所以它的值可以被多个函数所更改,看情况使用。当一个函数运行完它不会马 上消失,会等到整个程序结束时才释放内存地址。
注意:函数中的形参也是有局部变量的性质
void computeArea(double r){//即此时的r也就是一个局部变量,应为r的作用域范围就是computeArea函数之内,在它的外面是用不了computeArea括号内r的值的. }
下面我们通过例子来详细看到局部变量和全局变量的区别;
#include <stdio.h> void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略 void fun2(void); int main() { fun1(); fun2(); } //全局变量和局部变量使用位权:当一个函数中有这个变量的局部变量,则优先使用局部变量,如fun1, //当一个函数内,没有找到此变量,则会在函数头之前找,看看有没有这个变量存在,如果有则访问它的内存地址。 int x = 3;//全局变量x,在主函数中你无法访问x,因为它定义在main函数之后 void fun1(void) {//fun为function(函数)的缩写,括号内的void可以省略,下同 int x = 1, y = 2;//局部变量,只要局部变量x,y出了这个函数,就释放了x和y的内存地址,就消失了。 printf("x = %d,y = %d\n", x, y);//结果:x = 1,y = 2 } void fun2(void) { int y = 4; printf("x = %d,y = %d\n", x, y);//此函数未找到局部变量x,所以它会在函数头( void fun2(void) )找名为x的变量,所以用全局变量x = 3 //结果:x = 3,y = 4 }
下面我们清楚了一般的使用顺序,我们再加深点难度,看看改变局部变量和全局变量的值会不会有什么变化
#include <stdio.h> void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略 void fun2(void); int main() { fun1(); fun2(); } //全局变量和局部变量使用位权:当一个函数中有这个变量的局部变量,则优先使用局部变量,如fun1, //当一个函数内,没有找到此变量,则会在函数头之前找,看看有没有这个变量存在,如果有则访问它的内存地址。 int x;//全局变量x,在主函数中你无法访问x,因为它定义在main函数之后 void fun1(void) {//fun为function(函数)的缩写,括号内的void可以省略 int y = 2;//局部变量,只要局部变量y运行到了这个函数末尾,即在这个函数内,后面没有代码了,就释放了y的内存地址,就消失了。 printf("x = %d,y = %d\n", x, y);//结果:x = 0,y = 2 ++x;//等价于x = x+1; x = 0 + 1; ++y;//等价于y = y+1; } void fun2(void) { int y = 4; printf("x = %d,y = %d\n", x, y);//此函数未找到局部变量x,所以它会在函数头( void fun2(void) )找名为x的变量,所以用全局变量x = 3 //结果:x = 1,y = 4 }
解析:先调用fun1,printf语句时,找到了局部变量y,x未找到,所以向上找,找到了全局变量x,因为x未赋值,所以默认为0;
之后将x,y的值各加1,我们这里要清楚,这里的x是全局还是局部,很明显,x定义在外面,所以x是全局变量,如上所说,全局变量当一个函数运行 完它不会马上消失,会等到整个程序结束时才释放内存地址。所以x的值现在是1,而且是全局变量。
y是个局部变量,函数fun1运行完即释放内存,不会影响fun2中的局部变量y。所以在fun2中,编译器找到了全局变量x,值为1,局部变量y,值为4则 输出。这是fun2函数输出之后,变结束运行,注意这时候全局变量并没有释放,因为main函数还在运行,main函数之后也没有语句了,整个程序结 束,同时全局变量x释放内存。也就是说全局变量是跟main函数同存亡的,虽然main函数在这个程序里访问不到全局变量x。
(2)动态变量和静态变量
动态变量:默认值是未知的,不一定是0,当动态变量所属的函数和复合语句被执行时,该变量获得内存空间,函数或复合语句执行结束后立即释放该变量占据的内存空间,则该变量就不存 在了。
动态变量声明方式:这就是我们普通声明一个变量时的操作,如 int a = 5。
静态变量:默认值为0,静态变量占据固定的内存空间,只有整个程序执行结束后才会释放该变量占据的内存空间。(是否和之前的全局变量有相似之处)
静态变量声明方式:static 数据类型 标识符 = 值; ,如 static int a = 5;
下面我们通过示例来看看两者之间的区别
#include <stdio.h> void fun1(void);//即空类型和空参数,说明不需要返回值,也不用参数传入,所以主函数只要调用它们就可以了,括号内的void可以省略 int main() { fun1();//结果:2,2 fun1();//结果:3,2 } void fun1(void) { static int a = 1;//静态变量,只有整个程序执行结束后才会释放该变量占据的内存空间,也是与main函数同存亡的。 int b = 1;//动态变量,函数或复合语句执行结束后立即释放该变量占据的内存空间。 ++a; ++b; printf("a = %d,b= %d\n", a, b);/*执行这句后,a的值为2,b的值也为2,函数执行结束 a是静态变量,值还会保留着,而且main函数也没有结束,因为还有一句fun1();还没调用 b是动态变量,所以被释放了内存空间,此时b就仿佛从程序中消失了一般。 第二次调用,因为之前a的值是2,在+1即为3了,b的值是刚刚声明的为1,所以+1仍然为2 */ }
(6)生成随机数
简介:其实,计算机中的随机数也是通过一些复杂算法推算出来的一个整数,所以这并不是我们理解中的随机数,只是一个伪随机数,同时它也是根据一个数,我们称它为种子放入公式得出来的,只要种子值相同,“随机”出来的数当然也是相同的.
(1)生成:使用stdlib.h头文件的rand()函数
(2)格式:int rand(void)
此函数返回一个0~RAND_MAX范围内(包括0和RAND_MAX)的整数。RAND_MAX是stdlib.h头文件中声明的常量。根据计算机和编译器的不同都有可能不同。
#include <Stdio.h> #include <stdlib.h> int main() { int i; printf("RAND_MAX = %d\n", RAND_MAX); for (int i = 0; i < 5; ++i) { printf("%d: %d\n", i, rand()); } } /* 结果: RAND_MAX = 32767 0: 41 1: 18467 2: 6334 3: 26500 4: 19169 再执行一次 RAND_MAX = 32767 0: 41 1: 18467 2: 6334 3: 26500 4: 19169 */
解析:实际上,rand()函数生成的伪随机数是由种子值产生的,即rand函数使用一个种子值控制随机数的生成,该”种子“的默认值为1。使用同一种子值就总会得到相同的随机数序列。
解决方法:我们此时只要改变种子值,这样产生的随机数序列也会发生改变,此时我们需要stdlib.h头文件中的srand(seed)函数来改变,传入的参数seed为unsigned int类型
#include <Stdio.h> #include <stdlib.h> int main() { unsigned int seed;//无符号整数,取值范围是0,正整数。 printf("输入一个种子值:"); scanf("%u", &seed);//%u是无符号整型的格式符 srand(seed); for (int i = 0; i < 5; ++i) { printf("%d: %d\n", i, rand()); } } /* 结果: 输入一个种子值:2 0: 45 1: 29216 2: 24198 3: 17795 4: 29484 //第二次输入 输入一个种子值:3 0: 48 1: 7196 2: 9294 3: 9091 4: 7031 */
由此可见,但是这样未免太麻烦了,所以我们需要一个东西来实时改变种子值,我们这是就可以调用time函数,它会返回时间。
同时,我们也要记住一个公式,用于生成a~b范围内的随机数,通常可以使用表达式a+rand()%(b-a+1)来表示,
例如,想生成1~100之间的随机数,1+rand()%(100)此表达式可以表达,
#include <Stdio.h> #include <stdlib.h> #include <time.h> int main() { srand(time(NULL));//固定格式 int a, b; int count = 0; scanf("%d %d", &a, &b);//用于确定随机数的范围 while (count != 15){//用于输出15个随机数 printf("随机数:%d\n",a+rand()%(b-a+1)); count++; } } /* 结果: 1 100 随机数:88 随机数:31 随机数:18 随机数:28 随机数:56 随机数:75 随机数:61 随机数:84 随机数:3 随机数:32 随机数:45 随机数:6 随机数:91 随机数:64 随机数:8 */