大家好这里是三岁,C语言是各类语言的基础,由于个人学习需要特别开设了C语言基础专题,内容不一定深入,都是基础知识,还是白话C语言,最简单的方式带领大家学习不是那么容易理解的C语言!
函数
什么是函数呢???
和数学里面的一样?y=kx+b?
我认为在编程中这种理解不一定对。整个编程的内容都算是数学中的函数,都是数据处理的过程。(个人理解)
那么我们说的函数是什么呢?
我认为是编程人员偷懒的法宝!
记得编程届有这样子一个说法:一段代码只重复3遍以上就应该封装成一个函数,这样子可以减少很多代码量。
只要把一些具有相同规律的内容或具象化的模块进行打包封装形成一个函数,这样子就可以简单的调用,增加代码的可读性同时易于维护。
在C语言中函数具有两方面的用途:
- 第一种:字面意思:即求得一个函数的结果。
- 第二种:完成一个具体的功能,相当于一个子函数。
要求一个函数的结果的值或者要运行该函数来完成相应的功能,则称该函数被掉用或调用了该函数。
函数的分类
1、用户使用角度:
- 标准函数(库函数):
系统定义的不需要用户自己去定义,可以直接使用他们。如:sin()、sqrt()、fabs()- 用户自定义函数:用户为了特定需求去完成的函数。
2、从函数类型分类
- 无参函数:函数定义的括号中没有参数
- 有参函数:函数名后面的括号没有参数
白话:两个函数的分类标准不一样
其实就是一个是系统写好了的可以直接使用。(类似于python中的自带类库函数)
另外一个就是自己或者开源开发者提供的(类似于第三方类库)
还有一种就是看看函数有没有输入数据,有就是有参的没有就是无参的
具体的还是接下去看看吧
函数的定义
组成:函数说明部分和函数体部分。
函数说明部分主要组成有函数类型、函数名和形式参数等三部分。
函数体主要就是函数内的局部变量的声明和执行语句组成。用来完成具体的功能。
- 无参函数
类型说明符 函数名(){ 局部变量声明; 函数体(语句执行部分); }
举例:
void print_hello(){ printf("hello world"); }
- 有参函数
类型说明符 函数名(形参列表){ 局部变量声明; 函数体(语句执行部分); }
int max(int x, int y){ int z; if(x>y){ //判断x和y z=x; //成立就把x赋值给z } else{ z=y; //不成立就把y赋值给z } return z; //返回z }
- 空函数
类型说明符 函数名(形参列表){ }
特殊的函数,实际意义,起到占位符的作用,待后续添加。
类似于python中的pass
说明:
- 函数类型可以是合法的数据类型说明符用于确实函数的类型
- 函数名可以是任意的合法标识符(数字字母下划线,其中数字不可开头)
- C语言可以有多个函数组成但是main()(主函数)有且只有一个
- 函数之间可以相互调用,但是不能够嵌套不能够调用主函数(main())
形参和实参
名词解释:形参和实参
形参和实参直接白话吧
形参就是我们定义的函数后面括号里面的内容如上面max()里面的int x,int y一样他们有具体的定义和内容吗,但是他们门没有实际的意义,更加类似于一张板凳,上面有姓名标签,只能够对应的“人”坐上去。
实参:指的是调用函数过程中实际赋值给函数的值,如max(12,5)这里的12和5就代表实际参数。他们就是对应做到形参板凳上的“人”,当然数据类型要相同不然会报错或者结果出现偏差。
返回值 — return
如果函数调用过程中需要返回数据就需要使用return
函数不然默认没有返回值。让我们看看返回值有哪些要求。
- 函数没有说明类型的时候以整型处理
- 返回值以定义的函数类型为准,不相同的进行强制转换(注:这里可能会产生数据偏差)
- 如果不调用则没有返回值
- 如果不使用返回值的话可以使用
void
类型进行定义
函数的调用
格式
函数名(实参列表)
- 如果是无参函数不需要添加实参列表
- 形参和实参需要一一对应,个数数据类型也要也要,实参之间需要用
,
隔开。
调用:需要函数名加括号有参的要加参数列表,这个函数可以是独立的语句,可以是一个函数的实参等
main{ print_hello(); max(2,max(5,6); c=max(12,11); print("%d",c) }
结果:
hello world
6
12
调用函数的声明
当需要调用非本文件的函数时就需要使用调用声明。
如调用库函数:
需要在开头添加#include
命令
例如
#include<stdio.h>
以上是非本文件的函数,如果是本文件的函数呢?
首先看是否在主函数以前,如果是则不需要,如果不是就需要进行提前声明(如果是整数和字符型数据则不需要!)。
举例:
#include<stdio.h> int main() { int x=12,y=3,z; //定义数值及初始化值 z=add(x,y); //调用值 printf("%d\n",z); //输出结果 } int add(int x,int y){ //定义add函数 return x+y; //返回结果 }
这里自定义函数在主函数后面但是他是整型数据所以可以不需要重新定义。
如果把int类型改成float呢?
#include<stdio.h> int main() { int x=12,y=3,z; z=add(x,y); printf("%d\n",z); } float add(int x,int y){ return x+y; }
发生了数据引用冲突。
解决办法一:
#include<stdio.h> int main() { int x=12,y=3,z; float add(int x,int y); //添加定义 z=add(x,y); printf("%d\n",z); } float add(int x,int y){ return x+y; }
在main函数添加一个定义值,相对应做了一个全局的声明,然后就可以继续执行了。
方法二:
#include<stdio.h> float add(int x,int y){ return x+y; } int main() { int x=12,y=3,z; z=add(x,y); printf("%d\n",z); }
把add函数的位置上调这样子的话就可以在使用前进行编译。
方法三:
#include<stdio.h> float add(int x,int y); //提前定义声明 int main() { int x=12,y=3,z; z=add(x,y); printf("%d\n",z); } float add(int x,int y){ return x+y; }
局部变量和全局变量
函数体内说明的变量只适用于该函数内,而且只在声明以后有效,这样子的被称为局部变量。
C语言规定如下:
- main函数中定义的变量也是局部变量,只能够在主函数中有效。
- 函数定义时的形式参数也是局部变量,只能够在说明他的函数中使用。
- 不同的函数可以有相同的局部变量,他们不会相互影响,代表不同的对象
- 在复合语句块中定义的变量只能够在复合语句块中有效。
白话:
没有进行全局变量的内容只有在所在的大括号(花括号)中有效果,离开了大括号则无意义。需要再重新进行定义。
全局变量:需要再文档前进行声明,这样子,声明后面的全部范围都有效。
当局部变量和全局变量名字相同时只有局部变量有效
变量的存储类型
除了之前提到的数据类型还需要了解变量的存储类型。
存储类型 | C语言声明词 |
动态存储 | auto |
静态存储 | static |
外部存储 | extern |
寄存器变量 | register |
如果用存储的时间来考虑就是静态存储
和动态存储
静态存储就相当于主席台上的位置,一场活动都是专属的座位,需要到活动结束以后才能够释放。
动态存储就好像是场地上的座位,按照需求进行一个动态的分配的,在人离开凳子后(函数调用结束后)系统就会收回然后分配给下一个需的人。
变量的存储类型声明:
存储类型 数据类型 变量名
1、自动变量(动态变量)(auto)
格式:
auto 数据类型 变量名
示例:
auto int a;
数据存储中如果没有使用存储类型的声明则默认为auto
auto
存储在动态存储区,主要是指函数的动态局部变量
和形式参数
auto
在函数调用开始分配内存,函数调用结束释放内存。
普通的内部变量均隐含的被认为是auto
类型,一般很少使用auto
特异说明。
2、局部静态变量(static)
格式:
static 数据类型 变量名
示例:
static int a;
静态变量存储在静态存储区,在其所在的程序和函数中是永久变量,如果在调用函数后想保留值继续下次使用可应该为静态存储。
示例:
#include<stdio.h> int f(int b){ auto int a=0; // 定义动态变量 a static int c=3; // 定义静态变量c a+=1;c+=1; // a、c完成自加 return (a+b+c); // 返回a、b、c的和 } int main(){ int b=2,i; for(i;i<=3;i++){ // 循环 printf("%d\n",f(b)); // 输出f()的运行结果 } }
次数 | a的值 | b的值 | c的值 | 返回值 |
第一次开始 | 0 | 2 | 3 | / |
第一次结束 | 1 | 2 | 4 | 7 |
第二次开始 | 0 | 2 | 4 | / |
第二次结束 | 1 | 2 | 5 | 8 |
第三次开始 | 0 | 2 | 5 | / |
第三次结束 | 1 | 2 | 6 | 9 |
第四次开始 | 0 | 2 | 6 | / |
第四次结束 | 1 | 2 | 7 | 10 |
解析:
a值是f()函数的局部动态变量。从上图可以看出 在f()函数结束以后内存释放,再次运行后重新赋值
c值是f()函数的局部静态变量、从上图可以看出 在f()函数结束以后内存没有释放,再次运行后还保存了上次的数据b是主函数的局部动态变量,虽然是局部变量但是分为两部分。
- 第一部分是主函数定义的局部动态整型变量
- 第二部分是给f函数的实参,转变成了f函数内部的局部动态整型变量
两次变量由于主函数没有结束最后的结果也是没有发生改变,直到程序结束以后才释放内存。
3、寄存器变量(register)
格式:
register 数据类型 变量名
由于数据存储在运行内存运算速度会快的多,可以提高执行效率。所以有了寄存器变量。
要求:
- 1、寄存器变量的类型:
int
、char
或者是指向这两种的指针。 - 2、仅适用于函数的局部变量和函数的形式参数。
- 3、C语言规定寄存器的数量不能够超过3个,多余的按照auto处理。
4、外部变量(extern)
格式:
extern 数据类型 变量名
在定义全局变量时如果没有特别声明,则默认为extern
。
系统只给extern
类型分配一次变量,如果已经分配会寻找同名的数据进行处理,不会再次分配。
5、静态全局变量(static)
格式:
static 数据类型 变量名
这个和上面的区别类似于public
和private
外部变量是公共的而静态全局变量是私有的。
递归函数
递归函数就是函数直接或间接调用自己形成类似于套娃的内容。如果设计得当是一个比较好的程序,设计不好,那就是bug聚集地和死循环乐园,容易被老板开除。
需要满足下面3个条件:
- 原问题能够向另外一个更加简单的问题进行转换
- 转换后的问题和原问题求解类似
- 转换后能后有一个终止条件
举例:最经典的阶乘问题
#include<stdio.h> long fac(int n){ //定义fac函数 long f; printf("n=%d\t",n); //输出当前n的值(大->小) if(n<0){ printf("\nn<0,数据错误!"); //判断是否小于0 f=0; //赋值为0 } else if(n==0)f=1; //最后停止的标志 else f=fac(n-1)*n; //数据没问题调用自己继续执行 printf("\n 返回:n=%d\tf=%d",n,f); //当调用到n==0后 开始执行 return f; } main(){ int n; long y; printf("需要输出的轮数:"); scanf("%d",&n); y=fac(n); //调用fac函数 if(y!=0){ //y!=0就输出 printf("\n%d!=%ld\n",n,y); } }
该方法比for循环占用的时间更短,相对来说更加合适,但是和技术想法等都有关系。
就上面的函数停止条件是else if(n==0)f=1; //最后停止的标志
这一句以后才停止。如果这里不把握好就会出现与预期不符的情况。
好啦,这次的函数就到这里,量比较大,如果是第一次学习语言,需要好好理解。相当于就是把经常用到的东西或者比较繁琐的东西单独处理,然后再到main函数进行组装的一个过程。
预告:下一课 :数组!