关于c语言中对函数相关基础概念的理解

目录:

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才会申请内存空间。传参的过程称为形参的实例化,也可以理解为形参是实参的一份拷贝。形参的改变不会影响实参。

五.return语句

再刚刚初学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.形参与实参的个数要匹配,对应;(数组)

七.嵌套调用和链式访问

7.1嵌套调用 

嵌套调用其实就是函数之间相互调用,正因为有了多个函数互相调用,协助,使得在一个工程里面,开发效率变得更高。

假设:随意输入一个年份,计算该某月有多少天。

首先,我们要理清楚思路,有些年份是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;
}

}

这样应该比较好理解,就是一个真假的问题。

7.2链式访问

所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数,像链条一样串起来一样就是链式访问。为了方便理解,下面以一段简单的链式访问的代码为例:

,为什么打印的是4321呢?

首先从最内层的printf开始,所以先打印43,这是毫无疑问的,接着第二个printf,由于链式访问是⼀个函数的返回值作为另外⼀个函数的参数,所以最里面那个打印43,返回2。所以第二个printf打印2,返回1。所以最外层printf打印1。

所以这段代码在屏幕上打印4321。

八.函数的声明和定义(相关拓展:多文件当中如何适当隐藏代码)【vs】

8.1 单文件

顾名思义,也就是在一个文件夹当中。

不过在这里,还是不得不多嘴一句,函数的定义要放在函数调用之间,如果放在后面,则需要在前面对函数进行声明一下。

如:int year(int y),就是函数声明。

8.2 多文件

众所周知,在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.

因为这个提到了静态库,并且讲的比较潦草,就适当看看吧。

8.3 static和extern

考虑到这篇博文的内容过于冗长,那关于这里的知识点,就下篇博文再讲。

  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值