目录:
1.函数的概念
2.库函数
3.自定义函数
4.形参和实参
5.return语句
6.数组做函数参数
7.嵌套调用和链式访问
8.函数的声明和定义(相关拓展:简述多文件当中如何适当隐藏代码)
相信大家对函数并不陌生,在数学上尤为多见。其实c语言也一样,不过略微不同的是c语言当中的函数是指一小段完成特定任务的代码,一个较大的工程是由若干个函数组成,正因为有了函数的调用和引用,使得开发效率大大提高。
关于库函数相信大家并不陌生,如常见的printf(格式化打印),scanf(输入函数)。c语言标准中规定了c语言的各种语法规则,规定了一些常用函数的使用标准,称为标准库,不同的产商根据标准库,对相关函数进行实现,这些函数被称为库函数。
不同的库函数根据功能的不同被包含在不同的头文件当中,例如在使用printf,scanf等函数时,需要包含头文件#include<stdio.h>。
如果大家有兴趣可以去相关官网上面去查找资料:
库函数相C/C++官?的链接:https://zh.cppreference.com/w/c/header
c/c++官方网站:https://zh.cppreference.com/w/c/header
cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/
对于第一个和第二个网站,如果是英文版,可以将网址当中的zh改为en。
#include <stdio.h>
#include <math.h>
int main()
{
double d = 16.0;
double r = sqrt(d);
printf("%lf\n",r);
return 0;
}
上面这段代码是求平方根,使用sqrt库函数时需要包含对应的头文件<math.h>。
在对库函数有一个基本的了解后,下面就对自定义函数进行探讨,在一个工程里面,如果仅仅使用库函数这是远远不够的,这时候就需要我们自定义函数来完成相关代码的需求。
自定义函数的基本形式如下:return tapy(函数返回类型) name(根据需求取有意义的名称) (形式参数)。{}(花括号里面是函数实现的逻辑,也被称为函数体)
如:void menu(void) 这个就是一个自定义函数,menu是函数名称,void表示为空,即返回给主函数类型为空,以及不需要传回任何参数,具体在后续博文当中会给大家介绍。
那么我们究竟该如何理解自定义函数呢?
就如工厂一样,将参数1,参数2比做原材料1,原材料2。函数体比喻成加工工厂,函数返回的值比作生产的产品。
,如上图示。
函数体就是处理数据的逻辑,将参数通过函数体处理,达到我想要目的。
例如,写一个加法函数,完成两个数的相加。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int add(int x,int y)
{
return x+y;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
int ret = add(a,b);
printf("%d",ret);
return 0;
}
以上面这段代码为例:我在主函数里面自定义了一个加法函数add(),用int ret来接收add函数运算过后,返回的值。然后打印ret,即为两个数相加的和。
在看看上面的自定义函数,将主函数里面scanf输入的值用int x,int y来接收,这叫传参。原来x,y成为形式参数,a,b为实参。形参不占用内存空间,实参占用内存空间(后续博文会详细介绍),return为返回的意思,return x+y为返回x+y的值,以整数形式int返回,用int ret接收,最后打印。
关于形参与实参,我们以上面代码为例,调试,监视一下变量,观察如下:
,先按f10调试窗口,这时候已经开始调试了,再找到调试--窗口--监视--监视1,
输入想要监视的变量,以及查看想要监视变量的地址,接下来按f11进入到自定义函数里面,添加需要监视的变量x,y以及x,y变量的地址。
,
当我们添加变量时发现,x,y变量的值跟a,b的值相等,但是a,b,x,y的地址各不相同。事实上x,y为形参(调用前不占用内存空间),a,b为实际参数(占用内存空间),当调用函数时,为了存储a,b传过来的参数,x,y才会申请内存空间。传参的过程称为形参的实例化,也可以理解为形参是实参的一份拷贝。形参的改变不会影响实参。
再刚刚初学c语言之时,经常会看到return 0;这样的代码,那么再这里我们继续探讨一下关于return的一些相关的知识概念。
1.return语句后面可以是数值,也可以是一个表达式,如果是一个表达式,则先执行表达式,再返回表达式结果。就比如说我们经常写的main函数,return 0;返回一个值。以及我们再上面提到的自定义加法函数,return x+y;返回一个表达式。其实还有一种就是直接return;这种比较适合于返回类型为void的时候。比如如下代码:
#include <stdio.h>
void test(int n)
{
if(n<=0)
return;
int i = 0;
for(i=1;i<=n;i++)
{
sum += i;
}
printf("%d",sum);
}
int main()
{
test(5);
return 0;
}
求数字1-5的和,当将实参的数值用n接收时,如果n不符合if语句判断的标准,那么return;直接返回。不传回任何数值。
2.return返回的值和函数返回类型不?致,系统会?动将返回的值隐式转换为函数的返回类型
如何理解这句话呢,就比如自定义了一个函数是整形,但是在返回值时却是return 1314.520(double类型数值,不是整形),那么编译器会自动将数值转化成整形类型的数值,也就是1314。
3.如果函数中存在if等分?的语句,则要保证每种情况下都有return返回,否则会出现编译错误。
举一个例子:自定义一个函数判断给出的数字是否为奇偶。
#include <stdio.h>
int test(int n)
{
if(n%2==0)
return 1;
else
return 0;
}
int main()
{
int ret = test();
printf("%d",ret);
return 0;
}
就像上面的代码:偶数返回1,奇数返回0。如果上面语句没有else,那么编译器会报错:不是每条路径都有返回值。
在上述传参过程中,我们多以具体数据作为参数,那么接下来就介绍以数组作为参数的传参。
为了更为直观以及更好的理解相关内容,我们设计两个函数,一个置换原函数数组为-1,另一个则将这个数组打印在屏幕上。
示例代码如下:
#include <stdio.h>
void exchang(int arr[],int n)
{
int i = 0;
for(i=0;i<=n;i++)
{
arr[i] = -1;
}
}
void print(int arr[],int n)
{
for(i=0;i<=n;i++)
{
printf("%d",arr[i]);
}
}
int main()
{
int arr = {0};
n = sizeof(arr) / sizeof(arr[0]);
exchang(arr,n);
print(arr,n);
return 0;
}
首先看我们main主函数,我们自定义了一个exchang函数,用来置换数组内容为1,另外我们还自定义了一个print打印函数。按照代码执行的顺序,一条语句一条语句的执行,我们定义数组初始化为0,用n记录数组的元素个数(个数=元素总长度 / 单个元素大小),然后再将这两个参数(数组以及数组元素)传给自定义函数,自定义函数用数组int arr[]接收,以及n接收数组元素个数。然后再exchang函数里面用一个小循环将数组每个元素替换成-1。然后当程序进入到print自定义函数时再用一个小循环,将数组里面每个元素打印出来。
在这里需要注意的是:
1.数组传参时,形参不会重新创建,也就是实参和形参数组其实是同一个;
2.形参与实参的个数要匹配,对应;(数组)
嵌套调用其实就是函数之间相互调用,正因为有了多个函数互相调用,协助,使得在一个工程里面,开发效率变得更高。
假设:随意输入一个年份,计算该某月有多少天。
首先,我们要理清楚思路,有些年份是365天,而有些年份是366天,那为什么会造成注意的差异呢?其实是因为有些年份是平年,二月只有28天,有些年份是闰年,二月有29天。那么找到差异后,我们理清思路,不妨自定义一个函数判断是否为闰年,再自定义另外一个函数,判断是几月,用if语句判断如果是二月且为闰年,则天数加1天,用一个数组存储所有月份的天数。下面开始实践:
1.首先写一个主函数,将相关变量以及函数定义好:
#include <stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d",&a,&b);
month(a,b);
return 0;
}
,接着再写自定义year()函数判断是否为闰年的代码逻辑:
int year(int y)
{
if((y%100==0)&&(y%4!=0)||(y%400==0))
{
printf("闰年");
return 1;
}
else
{
printf("平年/n");
return 0;
}
}
紧接着就是计算月份天数的逻辑,这里相比前面会略微有一点点复杂,因为我们要判断月份,还需要计算天数,但是不着急,万事开头难。我们不妨将所有月份都存入到一个数组里面,再判断数组元素是否符合条件,说干咱就干:
int month(int y,int m)
{
int arr[] = {1,31,28,31,30,31,30,31,31,30,31,30,31};
if(year(y)&&m==2)
int day = arr[m];
return day;
}
,首先在main主函数里面定义一个月份的函数,为什么不在主函数里面定义一个判断年份的函数呢,不着急,咱慢慢分析。首先定义变量a,b。定义一个month函数,将在屏幕上输入a,b的值作为实参传给month函数接收,并进行加工。在month函数里面,用一个数组将所有月份的天数存储起来,所以数组元素个数应该是12个才对,但是在上面代码数组却有13个元素,且第一个元素为1,这是为什么?原因很简单,因为当我们需要数组元素是arr[m],m从0开始,arr[0]代表第一个元素,也就是说,m不是从1开始,也就是比如我们想要二月的天数,应该为arr[2],如果我们不在元素最前面加一个数字,那么二月所对应的天数就应该为arr[1],这与我们输入的月份始终对不上号,在前面加一个元素(0,1,2......都行),输入的月份就可以与数组里面的天数一一对应。
解释完为什么需要13个元素后,接着看month函数,如果输入的为2月,那我们就需要判断如果闰年二月天数+1,如果为平年则直接调用原数组的元素28。也就是说我们还需要在month这个函数,判断年份性质,不妨在定义一个自定义一个判断年份的函数year(值得注意的是在自定义函数里面不可以重新定义函数,只能调用函数其他函数)。
我们在看到year函数里面,符合闰年条件的返回1,不符合返回0;返回的值到month函数里面去,用了一个&&的符号,如果返回1,为真(即为闰年),符合判断式,执行下面语句。若返回为0,不符合判断式,则不执行下面语句(也就是天数+1)。其实上面month函数代码还可以这样写:
Bool_ year(int y)
{
if((y%100==0)&&(y%4!=0)||(y%400==0))
{
printf("闰年");
return true;
}
else
{
printf("平年/n");
return false;
}
}
这样应该比较好理解,就是一个真假的问题。
所谓链式访问就是将?个函数的返回值作为另外?个函数的参数,像链条一样串起来一样就是链式访问。为了方便理解,下面以一段简单的链式访问的代码为例:
,为什么打印的是4321呢?
首先从最内层的printf开始,所以先打印43,这是毫无疑问的,接着第二个printf,由于链式访问是?个函数的返回值作为另外?个函数的参数,所以最里面那个打印43,返回2。所以第二个printf打印2,返回1。所以最外层printf打印1。
所以这段代码在屏幕上打印4321。
顾名思义,也就是在一个文件夹当中。
不过在这里,还是不得不多嘴一句,函数的定义要放在函数调用之间,如果放在后面,则需要在前面对函数进行声明一下。
如:int year(int y),就是函数声明。
众所周知,在c语言当中,以.h为后缀的文件为头文件,.c为后缀的叫源文件。就好比头文件是一项建筑工程的蓝图,而源文件为这项工程的具体实施方案。
函数的声明,类型放在头文件当中,而函数的实施放在源文件当中。
下面,我们来写一个简单的加法函数,以add命名。(以vs为例)
1.右击头文件,添加新建项,选择头文件
,类似的操作,创建一个add.c的源文件。
首先,在test.c的源文件当中写一个主函数:
2.在add.c源文件中,写这个加法函数实现的代码:
3.在add.h头文件中,对函数进行声明
。
当然,为了让代码跑起来,我们还需要在test.c源文件当中包含我们自己的头文件,如下:
,这样代码就能跑起来了。
多文件的目的是为了提高开发效率,下面我们借着多文件这个标题,
说说在多文件当中如何适当的隐藏自己的代码:
比如说,我们写了一个很牛逼的加法代码,某公司想要购买,那么我们如何才能让他在使用的同时,不看到我们文件的源码呢?
我们可以给他函数声明的头文件,把函数定义的源文件换成以.lib为后缀的静态库文件给他,这样他打开看到的是一堆乱麻,但却可以使用,
(以vs为例,但其他编译器思路类似,基本相同)。
1.将声明函数的头文件add.h,以及函数如何实现的源文件add.c移除(注意不是删除)出去。
然后再到当初创建该项目的文件路径底下去找:
2.新建一个项目,
,右击鼠标,添加,现有项,将准备好的加法函数的头文件,源文件添加进来
3.右击项目名称--属性--常规--应用程序下拉按钮--将静态库应用--确定,这时候就生成了以.lib为后缀的静态库文件,如果这时候我们ctrl+f5运行,发现编译器报错
,因为我们生成的是静态库,并且没有主函数。
4.再到该创建项目的路径底下找.lib文件,如果我们以记事本打开
,发现是一堆乱码,这个时候在将这个.lib文件拷贝一份,放到我们想使用这个函数的项目文件下面,这样就可以使用了。不过值得注意的是:在使用的时候,需要包含一下这歌文件,也就是加上这句代码:#pragma comment(lib,"add.lib"),并且注意是x64还是x86.
因为这个提到了静态库,并且讲的比较潦草,就适当看看吧。
考虑到这篇博文的内容过于冗长,那关于这里的知识点,就下篇博文再讲。