C语言专业总结(三)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: C语言专业总结(三)

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
*/
目录
相关文章
|
机器人 Linux C语言
C语言, C++ IO 总结. 一篇文章帮你透析缓冲区存在的意义, C, C++ IO的常见用法
C语言, C++ IO 总结. 一篇文章帮你透析缓冲区存在的意义, C, C++ IO的常见用法
C语言, C++ IO 总结. 一篇文章帮你透析缓冲区存在的意义, C, C++ IO的常见用法
|
存储 自然语言处理 算法
C语言学习前五章思维导图式总结(超详细,复习必备)
C语言学习前五章思维导图式总结(超详细,复习必备),源文件在 process  on(在线流程图)上面,同名,需要多多支持。
1074 1
C语言学习前五章思维导图式总结(超详细,复习必备)
|
算法 C语言
C语言第五章:循环结构程序设计总结。(超详细)
C语言第五章:循环结构程序设计总结。(超详细)
410 0
C语言第五章:循环结构程序设计总结。(超详细)
|
存储 人工智能 C语言
C语言第二章 数据类型,运算符和表达式总结【完美补充文字版】(超级详细)
C语言第二章 数据类型,运算符和表达式总结【完美补充文字版】(超级详细)
529 0
C语言第二章 数据类型,运算符和表达式总结【完美补充文字版】(超级详细)
|
存储 编译器 C语言
C语言专业总结(六)
C语言专业总结(六)
88 0
|
存储 编译器 C语言
C语言专业总结(五)
C语言专业总结(五)
72 0
|
搜索推荐 编译器 C语言
C语言专业总结(四)
C语言专业总结(四)
79 0
|
C语言
C语言专业总结(二)
C语言专业总结(二)
137 0
|
存储 编译器 C语言
C语言专业总结(一)
C语言专业总结(一)
84 0
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3