一.前言
本章为个人所学所理解的C语言中函数篇的相关知识,定有不足,还望大家多多指教。
二.什么是函数?
函数是一段可以重复使用的代码,用来独立地完成某个功能。
三.函数的分类:
函数可以分为库函数和自定义函数。
1.库函数
在说库函数之前,先给大家推荐一个网站,这个网站可以搜索到所有C语言中的库函数,它包含了这些函数的信息和如何使用的场景介绍,下面是它的网址: [cplusplus]https://legacy.cplusplus.com/
介绍:C语言在发布时已经为我们封装好了很多函数,它们被分门别类地放到了不同的头文件中(暂时先这样认为),使用这些函数时引入对应的头文件即可。这些函数是已经被编写的且效率极高,因此,为了某些功能的实现,引入库函数是首选。 比如: strlen strcpy memcet
1.1 strlen的作用为求字符串长度
这里放段代码展示其作用
#include <stdio.h>
#include <string.h> //引入包含strlen函数的头文件
int main()
{
char arr[] = "hello";
int ret = strlen(arr); // 将求出的字符串返回值用整型变量接受
printf("%d\n", ret);
return 0;
}
下面是运行结果 很明显strlen求出的值的确是“hello”的长度值。
1.2strcpy与memset函数的使用
可以说strcpy与memset有相似之处,在某些场景,这两个函数都能将其实现
1.2.1strcpy
通俗来说,strcpy是将一个字符串里的内容拷贝到另一个字符串里,值得注意的是,当一个字符串拷贝到另一个字符串里时,其第一个字符串里的字符串结束标志 “\0" 也被拷贝进第二个字符串里,这时第二个字符串里原有的内容就会被两个“\0"包围,从而打印第二个字符串时为第一个字符串的内容。
代码实现:
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "******";
char arr2[] = "abc";
strcpy(arr1, arr2); /// 将arr2打印到目的地arr1中
printf("%s\n", arr1);
return 0;
}
运行结果
1.2.2 memset
C 库函数 void *memset(void *ptr, int value, size_t num) 复制字符 value(一个无符号字符)到参数 ptr 所指向的字符串的前 num 个字符。
代码实现:
#include <stdio.h>
#include <string.h> // 用 memset 对应头文件
int main()
{
char arr1[] = "########";
memset(arr1, '*', 4); ///// 将4个‘*’字符复制到arr1当中
printf("%s\n", arr1);
return 0;
}
运行结果:
库函数还有很多,这里只是举的几个例子,如果要多的了解,可以访问上面给的网站。
2. 自定义函数
自定义函数是自己实现的为了完成整个程序某个功能所设置的,他能多次调用,服务于整个程序,并且自定义函数各模块之间尽量功能独立。
2.1 为什么要有自定义函数?
大家想想,如果所有的功能库函数都为你提供了,那还关程序员啥事? 自定义函数在某种程度上更高于库函数,他能体现一个程序员的水平如何。
2.2 自定义函数的组成与例子
ret_type function_name(para, *)
{
///// 函数体
}
ret_type //返回类型
function_name // 函数名
para // 函数参数
* // 另一个参数 ,待定义
例如:1.(计算两个整型变量的和)(Add)
#include <stdio.h>
// 这里返回值为整型
int Add(int a, int b) /// 用两个整型值接受传来的 a b ,这里可以重名
{
return (a + b);
}
int main()
{
int a = 10;
int b = 20;
// ret 接受返回值
int ret = Add(a, b); // 传参 求和
printf("%d\n", ret); // 打印和值
return 0;
}
运行结果: 例 2. 求一个整数的位数
#include <stdio.h>
int fun(int n)
{
int count = 0;
while (n > 0)
{
n = n / 10;
count++;
}
return count;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fun(n);
printf("%d\n", ret);
return 0;
}
运行结果: 这里函数名最好是取与功能相关的名字,这样更具可读性。
2.3 void说明:
如果一个函数的返回类型为void,表示这个函数没有返回值,一般这样使用可能是为了打印某个东西,也可能是将某个整型变量或者数组元素改变。 例如:这里将数组元素改变。
#include <stdio.h>
void ecg(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = 0;
}
}
int main()
{
int arr[3] = { 1,2,3 };
int sz = sizeof(arr) / sizeof(arr[0]); // 求数组 长度/元素个数
ecg(arr, sz); //// 这里我们将arr数组内容全改为0
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果: 这里我们将整型数组的内容全部改为 0 。
四. 函数的参数
1. 实际参数(实参)
实参为传递给函数的参数,它可以是常量,表达式,函数(这里实参作为另一个函数的返回值)等。在实参传递时,他必须要有确定的值以便于形参接受。
2.形式参数(形参)
形参是实参的一份临时拷贝,当一个函数被调用时,形参才被实例化,当函数结束时,形参也相继被释放。
五. 函数的调用
1.传值调用
例如:求两个数的最大值
#include <stdio.h>
int Max(int a, int b)
{
return (a > b ? a : b);
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int ret = Max(a, b); //// 这里传递的是 a b 的值
printf("MAX = %d", ret);
return 0;
}
运行结果:
2. 传址调用
当我们调用函数想要改变实参的值,这是我们应该使用传址调用,因为形参接受实参值时会开辟另外的空间来存放,这时想要通过改变形参来达到改变实参的目的,就不会实现,因为地址不同,所以我们要传地址。 例如:交换两个整型值
#include <stdio.h>
void ecg(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换前:%d %d\n", a, b);
ecg(&a, &b);
printf("交换后:%d %d\n", a, b);
return 0;
}
运行结果:
六. 函数的嵌套调用和链式访问
1.函数的嵌套调用
这里用代码来展现:
#include <stdio.h>
int ADD(int c) {
return 2 * c;
}
int doubleAdd(int a, int b)
{
int c = a + b;
int rets = ADD(c); //// 调用 2*实参 的函数
return rets;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int ret = doubleAdd(a, b); //// 调用函数
printf("%d\n", ret);
return 0;
}
2. 函数的链式访问
把一个函数的返回值作为另一个函数的参数 这里用一段典型代码来表示:
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
补充:printf函数返回值是一个数的位数 ,也就是有几个数。
七. 函数的声明和定义
1. 函数的声明
(1)在一个项目中声明一般在头文件里,定义与使用一般在不同的 .C 文件中。
(2)函数的使用一定要先定义后声明后使用,如果一个自定义函数模块放在main函数后面,那么在main函数前一定要声明,不然当你在main函数中使用定义的函数时,编译器从上至下编译你的代码不会先编译你的函数定义的内容,这时就会报某某未定义错误。 (3)声明就是告诉编译器函数叫什么,参数是什么,返回类型是什么。
2. 函数的定义
定义就是函数功能的实现,使它能够完成项目的某个模块。 前面有定义的例子,这里就不举例了。
八. 函数的递归
1. 什么是递归?
递归实际上就是程序自己调用自己,它常常可以把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,这可以大大减少代码量,所以递归的主要思考方式在于:把大事化小。
2. 递归必要的两个条件
1.存在限制条件,当满足这个限制条件后,递归便不再进行。 2.每次递归第调用都要越来越接近这个限制条件。
3. 递归练习
1.求n的阶乘
#include <stdio.h>
int jc(int n)
{
int i = 0;
if (n <= 1)
{
return 1;
}
else
{
return n * jc(n - 1);
}
}
//递归
int main()
{
int n = 0;
scanf("%d", &n);
int ret = jc(n);
printf("%d", ret);
return 0;
}
2.正序打印一个整型值的每一位
#include <stdio.h>
void Print(int n)
{
if (n > 0)
{
Print(n / 10);
}
else
{
return 0;
}
printf("%d ", n % 10);
}
int main()
{
int n = 0;
scanf("%d", &n);
Print(n);// 正序打印一个整数的每一位
return 0;
}
3. 递归实现strlen求字符串长度
#include <stdio.h>
int my_strlen(char* str)
{
if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
else
{
return 0;
}
}
int main()
{
char arr[] = "hello";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
4. 递归的局限性
值得注意的是:因为递归有以上良好的功能,所以其思考难度较大。
有时,递归使代码量大大减少的同时也增加了程序的运行难度(运行效率大大降低,如:求第n个斐波那契数),并且其可读性没有非递归那么好,所以递归也要好好思考来选择。
如果递归使用不当,就会出现栈溢出(“Stack overflow”)现象,这是因为每次函数调用都会在内存的栈区开辟一个自己的空间,如果递归过多导致内存栈空间开辟过多,这时就会出现溢出现象。
九. 总结
总的来说,函数在C语言中具有相当高的地位,它的存在使得一个项目更具模块化,便捷化,实效性和可读性,因此,我们在学习C语言当中,应养成函数定义这一习惯,并将使用函数的思维融会贯通。