在数学中,函数是一种关系,它将一个或多个输入值映射到唯一的输出值。而在咱们计算机编程语言中,也有函数这么一说,维基百科对于函数的定义:**子程序**
(1):在计算机科学中,子程序是一个大型程序中的某部分代码,由一个或多个语句块组成,它负责完成某项特定的任务,而且相较于其他代码,具有独立性,可以重复使用。
(2):函数一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
在数学中对于函数有分类,例如指数函数,幂函数,对数函数等等,在计算中也是如此,对于函数同样也有分类。
为什么在计算机中会有库函数呢?
譬如在学习C语言编程的时候,总是在一个代码编写完成之后十分渴望地想知道结果,想把结果打印到我们的屏幕上看看。这个时候就会频繁地使用一个功能:将信息按照一定的方式打印在屏幕,那么此时C语言就提供一个库函数printf来供我们使用。
除了printf函数,还有其他各种各样的库函数,这里博主简单地介绍几个。
在编程的过程中会频繁地做一些字符串的拷贝工作,那么此时可以使用strcpy函数来完成此项工作。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
char arr1[2000] = "0xxxxxxxx";
char arr2[2000] = "abcde";
//拷贝字符串,将字符串arr2指向的空间,拷贝放到字符串arr1中(包括空字符串即\0)
strcpy(arr1, arr2);
printf("%s", arr1);
return 0;
}
strcpy函数需要传入两个参数,第一个参数为目的地址,第二个参数为源头地址,这段代码就会将字符串arr2指向的空间拷贝放到字符串arr1中(也包括\0)并且返回的是目标空间的地址。注意:使用strcpy函数需要包含对应的头文件(string.h)哦!
在编程中也会涉及计算,总是会计算n的k次方这样的计算,那么这时候我们可以使用库函数**pow。**
#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
int n = 0;
scanf("%d", &n);
int k = (int)pow(10, n);
printf("%d", k);
return 0;
}
我们可以看到pow函数需要传递两个参数,第一个参数为基本数,第二个参数为幂,两个参数均为double类型,然后函数返回类型也是double类型,博主由于使用的是int类型,因此对其进行了强制类型转换!
(1):IO函数。
(2):字符串操作函数。
(3):字符操作函数。
(4):内存操作函数。
(5):时间/日期函数。
(6):数学函数。
(7):其他库函数。
这里博主推荐个网站,叫cplusplus,里面有C语言的各个函数哦,uu们下来之后可以自己去了解下,链接:https://legacy.cplusplus.com/
除了库函数,还有自定义函数,自定义函数与库函数一样,有函数名,返回值类型和函数参数,但是不一样的是,这些都是由我们程序员自己来设计,下面博主将举个简单的例子!
ret_type fun_name(para1,*)
{
//语句项
statement;
}
/*
ret_type :函数的返回类型
fun_name :函数名
para1 :函数参数
{} :函数体
*/
#define _CRT_SECURE_NO_WARNINGS
#include
#include
//形式参数
int Product(int x ,int y)
{
return x * y;
}
int main()
{
int value1 = 0;
int value2 = 0;
scanf("%d %d", &value1, &value2);
//实际参数
int result = Product(value1, value2);
printf("result = %d ", result);
return 0;
}
我们看这段代码,这里博主定义了Product函数来求两个数的乘积,既然是乘积那么这个函数的返回类型就应该是int类型,然后定义好了以后,我们使用()函数调用操作符来调用函数,并且要传入参数value1和value2,我们既然传入了参数,那么相对应地就应该接收这个参数,那么在接收参数的时候,要确定接收的参数的数据类型并且要与传入的参数的数据类型一致,最后使用一个变量result来接收Product函数的返回值也就是两个数的乘积。
#define _CRT_SECURE_NO_WARNINGS
#include
void swap(int x,int y)
{
int z = x;
x = y;
y = z;
}
int main()
{
int value1 = 0;
int value2 = 0;
scanf("%d %d", &value1,&value2);
printf("交换前:value1 = %d,value2 = %d\n", value1, value2);
swap(value1,value2);
printf("交换后:value1 = %d,value2 = %d\n", value1, value2);
return 0;
}
我们看这段代码,这里定义了一个函数来交换两个数的值,然后我们运行下会发现并没有交换,很多小伙伴就在想这是为什么呢?这里就涉及到博主接下来要讲的函数的参数和函数调用了!
函数的参数分为两种,实际参数与形式参数!
概念:真实传递给函数的参数
实参类型:可以为常量,变量,表达式,函数等等
无论实际参数是何种类型的变量,在进行函数调用时,它们必须有确定的值,以便将这些值传送给形式参数。
概念:形式参数是指函数名后面括号中的变量,**因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),**所以叫形式参数。形式参数在函数调用完成之后就自动销毁了。因此形式参数就只在函数中有效。
#define _CRT_SECURE_NO_WARNINGS
#include
//形式参数
void swap(int x,int y)
{
int z = x;
x = y;
y = z;
}
int main()
{
int value1 = 0;
int value2 = 0;
scanf("%d %d", &value1,&value2);
printf("交换前:value1 = %d,value2 = %d\n", value1, value2);
//实际参数
swap(value1,value2);
printf("交换后:value1 = %d,value2 = %d\n", value1, value2);
return 0;
}
讲完函数的参数后,接下来就要讲就要将函数的调用,函数的调用分为两种,分别是:**传值调用**;**传址调用**。
函数的形参与实参分别占有**不同的内存地址**,**传值调用对形参的改变不会影响到实参。**
#define _CRT_SECURE_NO_WARNINGS
#include
//形式参数
void swap(int x,int y)
{
int z = x;
x = y;
y = z;
}
int main()
{
int value1 = 0;
int value2 = 0;
scanf("%d %d", &value1,&value2);
printf("交换前:value1 = %d,value2 = %d\n", value1, value2);
//实际参数;传值调用
swap(value1,value2);
printf("交换后:value1 = %d,value2 = %d\n", value1, value2);
return 0;
}
我们再回到最初的那个实现两数交换的代码,前面运行发现并没有进行交换,这里就是使用了传值调用,将value1与value2的值分别赋给了x与y,但是由于形参与实参分别在不同的内存地址中,因此在对其进行交换操作不会影响到实参,所以这也是为什么这段代码执行起来value1和value2还是原来的值。这里通过调试我们也能发现,形式参数x与y与value和value2的地址不同
那么有些小伙伴在想,那么到底该怎么实现这个两数交换呢?这里就得使用传址调用了,那么什么是传址调用呢?
(1):传址调用是将函数外部创建的变量的内存地址给函数参数的一种调用函数的方式。
(2):这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是说函数内部可以直接操作函数外部的变量
#define _CRT_SECURE_NO_WARNINGS
#include
//形式参数
void swap(int * x, int* y)
{
int z = *x;
*x = *y;
*y = z;
}
int main()
{
int value1 = 0;
int value2 = 0;
scanf("%d %d", &value1, &value2);
printf("交换前:value1 = %d,value2 = %d\n", value1, value2);
//实际参数;传值调用
swap(&value1, &value2);
printf("交换后:value1 = %d,value2 = %d\n", value1, value2);
return 0;
}
传址调用的话,就是将实参的地址传给形参,那么我们在之前讲过,用什么变量来存储地址呢?那就是指针变量了,因此形式参数使用两个指针变量来进行存储,通过调试,我们也能发现,指针变量x存储的是value1的地址,指针变量y存储的是value2的地址,这个时候我们对其交换就能实现啦!
所谓函数的嵌套调用,就好比定义了FunctionA与FunctionB两个函数,然后我们在主函数调用FunctionA函数,在FunctionA函数里头调用FunctionB函数;这就是函数的嵌套调用。
#define _CRT_SECURE_NO_WARNINGS
#include
void new_line()
{
printf("hello world\n");
}
void three_line()
{
for (int i = 0; i < 3; i++)
{
//调用new_line函数
new_line();
}
}
int main()
{
//调用three_line函数
three_line();
return 0;
}
这里博主就使用了函数的嵌套调用来打印三次hello world;通过在three_line这个函数中调用三次new_line函数。
**PS:函数能够嵌套调用,但是不能嵌套定义!如果说我没定义new_line函数的话,是不能在three_line函数里头去定义这个函数的**
概念:所谓函数的链式访问,是指将一个函数的返回值作为另外一个函数的参数。我们看下面这段代码
#define _CRT_SECURE_NO_WARNINGS
#include
#include
int main()
{
char arr1[20] = { 0 };
char arr2[20] = "hello world";
//链式访问:将一个函数的返回值作为另外一个函数的参数
printf("%d\n", strlen(strcpy(arr1, arr2)));
return 0;
}
strcpy函数是将源头地址的数据拷贝到目标地址中,同时会将源字符串的\0拷贝到目标空间,返回的是目标空间的起始地址。
strlen函数就是用来求字符串的长度,统计\0之前出现的字符个数!
我们看这段代码的运行结果是11,首先先将字符串arr2的内容拷贝到arr1,然后strlen函数就会统计出现在\0之前的字符个数再使用printf函数打印结果。
接下来博主将为大家介绍函数的声明与定义。
(1):告诉编译器一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
(2):函数的声明一般出现在函数的使用之前。要满足先声明后使用。
(3):函数的声明一般是放在头文件中的。
函数的定义是指函数的具体实现,交待函数的功能实现。
#define _CRT_SECURE_NO_WARNINGS
#include
//函数定义(定义也是一种特殊的声明)
int Product(int x, int y)
{
return x * y;
}
int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
//传值调用
int result = Product(x, y);
printf("%d ", result);
return 0;
}
这里博主就是使用了一个函数来实现两数的乘积,可以看到,博主是在调用Product函数之前对其进行了定义与声明,因为函数要先声明才能使用。
如果在未声明前进行了调用,我们会发现,当编译代码的时候,编译器会告诉我们这个函数未定义,那么此时我们要对其进行声明。
当我们在调用前对其进行声明,此时再次编译代码,编译器就不会报警告啦!除了上面这两种方式外,还有一种方式,就是在头文件里头对函数进行声明。
#pragma once
int swap(int * x, int * y);
#define _CRT_SECURE_NO_WARNINGS
#include
#include "swap.h"
int swap(int * x,int * y)
{
int z = *x;
*x = *y;
*y = z;
}
#define _CRT_SECURE_NO_WARNINGS
#include
#include "swap.h"
int main()
{
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
printf("交换前:x = %d,y = %d\n", x, y);
swap(&x, &y);
printf("交换后:x = %d,y = %d\n", x, y);
return 0;
}
这里要注意,使用头文件声明函数时,若想要调用,就必须得包含函数对应的头文件,包含声明函数的头文件是使用""包裹滴而不是<>,uu们在这里要注意哦!
我们运行这段代码发现结果还是能够实现交换的功能,在日后写代码的时候,uu们可以多这样子使用,这样子能够将代码进行模块化,方便日后更好地维护和管理哦!
讲完函数的声明与定义之后,接下来博主将为大家介绍函数递归,那么什么是递归呢?
程序调用自身的编程技巧称为递归。也就说函数自己调用自己称为函数递归。
递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中有直接或间接调用自身的。
只需少量的程序的就可以描述出解题过程的多次重复计算,大大减少了程序的代码量。
递归的主要思考方式在于:大事化小
递归虽然能大事化小,但是在写程序的时候,总不能一直递归吧,这样子很容易造成程序崩溃滴,所以递归也存在两个必要条件。
(1):存在限制条件,当满足这个限制条件的时候,递归便不再继续。
(2):每次递归调用之后会越来越接近这个限制条件。
uu们可能还对递归不是很理解,我们接下来通过这几段代码来具体讲解下
我们先分析一下这个思路,拿1456来举例,我要打印这个数的每一位,首先是先%10拿到个位,然后1456再/10得到145,接着145再%10拿到十位,然后145再/10得到14,14再%10拿到百位,然后14/10得到4,接着4再%10得到最后一位,也就是说所有的过程都变成了先%10再/10的过程,这样子周而复始,之前我们讲过这个递归有两个必要条件:(1):要有限制条件,(2):随着递归不断进行下去,要慢慢接近这个限制条件。,我们仔细分析下会发现,/10的前提是不是得是至少是个两位数呀,因为每次/10都会丢掉一位,这样子周而复始,直到只剩下一位的时候,这个时候%10得到这一位。
#define _CRT_SECURE_NO_WARNINGS
#include
void print(int value)
{
//递归的限制条件
if (value > 9)
{
print(value / 10);
}
printf("%d ", value % 10);
}
int main()
{
int value = 0;
scanf("%d", &value);
print(value);
return 0;
}
我们以2345为例,然后执行下这段代码,发现在屏幕上成功打印了这个数的每一位,如果uu们有不理解的地方可以看看博主下面画的图哦!
我们在来看一个问题,在数学中,有阶乘这么一个概念,譬如:5! = 5 * 4 * 3 * 2 * 1,这样子一直乘到1,假设求n的阶乘的话,那么就是 n * (n-1)!,然后(n - 1)! = (n-1) * (n-2)!,这样子周而复始,第n个数乘以第(n-1)个数的阶乘,一直乘到1,那么这个递归的结束条件是什么呢?我们仔细分析就会发现,每次乘,最后都是乘到1就结束了,当最后一个数的为1的时候,这个时候就可以结束递归啦!
#define _CRT_SECURE_NO_WARNINGS
#include
int Fib(int value)
{
//递归的限制条件
if (value == 1)
{
return 1;
},
else
{
return value * Fib(value - 1);
}
}
int main()
{
int value = 0;
scanf("%d", &value);
int result = Fib(value);
printf("%d的阶乘为%d\n", value, result);
return 0;
}
我们以6为例,求6的阶乘,会发现成功求出6的阶乘为720,uu们可以下面博主画的图来加强理解哦!
好啦,家人们,关于函数这块的相关细节知识,博主就讲到这里了,如果uu们觉得博主讲的不错的话,请动动你们滴滴给博主点个赞,你们滴鼓励将成为博主源源不断滴动力!