C语言学习-函数(3)

简介: C语言学习-函数(3)

思维导图:


1. 函数是什么?

函数就是子程序,就是一个大型程序中的部分代码,并具有相对独立性。


2. C语言中函数的分类:


2.1 库函数:

标准库-提供了C语言相关的库函数


那为什么要有库函数呢?


在编程的过程中,我们总是会用到printf去打印一个数;


或者是经常会用到scanf去输入一些值。


拥有了库函数,程序员就不用每次使用这些值得时候去写程序,直接调用库函数就行,


这样就大大提高了编写代码的效率。


2.1.1 如何学会使用库函数?

cplusplus.com - The C++ Resources Network

https://legacy.cplusplus.com/

这里推荐一个可以学习C语言库函数的网站。


简单总结,C语言常用的库函数:



我们参照网站的文档,可以学习库函数。



这是库string.h中的函数。



我们可以看到它的返回类型是char*类型的指针,函数名是strcpy,需要传两个char*类型的参数。


通过它的描述,我们可以得知这个函数是将字符source拷贝到‘目的地’destination,包括‘\0’字符。


这是对两个参数的具体解释。



函数的返回值是destination这个字符。


这样我们就大致了解了这个函数,可以进行简单的应用了。


#define _CRT_SECURE_NO_WARNINGS 1
#include
#include//引用该函数头文件
int main()
{
  char arr1[20] = { 0 };
  char arr2[] = "welcome to my blog!";//创建两个字符数组
  strcpy(arr1, arr2); //数组名代表的是数组首元素的地址
  printf("%s",arr1);  //我们运用函数将arr2的值拷贝到arr1中
  return 0;          
}

打印出来的结果是:


welcome to my blog!

当然,这并不代表我们已经精通这个函数了,重要的是多加练习。


2.2 自定义函数

不过,比库函数更重要的是自定义函数,顾名思义,这需要我们自己来设计函数。


比如说:我想设计一个函数找出两个整数的最大值:


//设计思路
//输入两个整数
//设计一个函数实现目标
#include
int get_max(int x, int y)//返回类型是int,而int x接收a的值,int y接收b的值
{
  return (x > y ? x : y);//函数的返回值
}
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  int max = get_max(a, b);//创建一个整形变量接收函数的返回值
  printf("%d\n", max);    //函数将a和b传值
  return 0;
}


输出的结果是:


输入:10 20
输出:20

这就是一个函数的基本形式。


再举一个例子:


设计一个函数交换两个整形变量的值:


#include
void exchange(int x, int y)//void表示函数没有返回类型,所以不用return。
{
  int tmp = x;
  x = y;
  y = tmp;
}   //交换两个值
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  exchange(a, b);
  printf("a = %d b = %d", a, b);
  return 0;
}


但是输出的结果是:


输入:3 5
输出:a = 3 b = 5

我们发现它并a和b没有交换各自的值,究竟是为什么呢?


这就要说到函数的参数。


3. 函数的参数

3.1 实际参数(实参):

我们传给函数的参数是实参;实参可以是:常量、变量、表达式、函数等。


但实参一定要是一个确切的数。


3.2 形式参数(形参):

形参是指函数名后面,接收我们传过去的参数的参数。


形式参数当函数调用完成之后就会自动销毁,类似于局部变量,形参只在函数内生效。


而且形参在创建时会拥有自己的内存空间,并拥有实参的内容,这也是上文出错的原因,


总结:所以形参可以被看成是实参的一份临时拷贝。


那我们该如何实现上文的那个函数呢?


这就要说到函数的调用规则了:


4. 函数的调用:

4.1 传值调用

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参。


4.2 传址调用

这种传参方式可以让函数和函数外边的变量建立起真正的联系,


也就是函数内部可以直接操作函数外部的变量,也就是实现所谓的远程操控。


//怎样才能让函数实现a,b值之间的交换呢?
#include
void exchange(int* pa, int* pb)//形参//创建指针进行接收
{                              //这样形参就能通过地址远程操控主函数中的变量
  int tmp = *pa;//tmp = a    //*解引用
  *pa = *pb;    //a = b
  *pb = tmp;    //b = tmp
}
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  exchange(&a, &b);//实参//我们可以将a和b的地址传给函数
  printf("a = %d b = %d", a, b);
  return 0;
}


输出的结果:


输入:3 5

输出:a = 5 b = 3

这样就能成功交换a和b的值了。


4.3 练习

趁热打铁,练习是巩固新知的最好方法。


1. 写一个函数判断一个数是不是素数:


#include
#include//函数sqrt的调用
int is_prime(int n)
{
  int j = 0;
  for (j = 2; j <= sqrt(n); j++)//sqrt是开平方函数:根号n
  {                            //试除法判断素数,运用sqrt函数提高代码效率
  if (n % j == 0)
  {
    return 0;//不是素数
  }
  }
  return 1;//是素数
}
int main()
{
  int i = 0;
  scanf("%d", &i);
  if (is_prime(i))
  {
  printf("是素数");
  }
  else
  {
  printf("不是素数");
  }
  return 0;
}

输出结果:

输入:100
输出:不是素数
输入:101
输出:是素数

2. 写一个函数判断一年是不是闰年:


#include
int is_leap_year(int x)
{
  return (x % 4 == 0 && x % 100 != 0 || x % 400 == 0);
}           //&&,||这两个符号返回的值就是0和1,可以直接返回
int main()
{
  int i = 0;
  scanf("%d", &i);
  if (is_leap_year(i))
  {
  printf("是闰年");
  }
  else
  {
  printf("不是闰年");
  }
  return 0;
}


输出结果:


输入:1000
输出:不是闰年
输入:2000
输出:是闰年

3.  写一个函数,实现一个整形有序数组的二分查找:


//代码思路:
//创建一个整形有序数组
//设置输入
//创建函数实现二分查找
#include
int binary_search(char* arr, int sz, int n)
{                  //arr数组名代表数组首元素地址,用指针接收
  int left = 0;
  int right = sz - 1;
  int mid = 0;
  while (left <= right)
  {
  mid= (left + right) / 2;
  if (arr[mid] > n)
  {
    right = mid - 1;
  }
  else if (arr[mid] < n)
  {
    left = mid + 1;
  }
  else
  {
    return mid;
  }
  }
  return 0;
}
int main()
{
  char arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  int n = 0;
  scanf("%d", &n);
  int sz = sizeof(arr) / sizeof(arr[0]);
  if (binary_search(arr, sz, n))
  {
  printf("找到了,下标是:%d", binary_search(arr, sz, n));
  }
  else
  {
  printf("找不到");
  }
  return 0;
}

输出结果:


输入:7
输出:找到了,下标是6
输入:0
输出:找不到

4. 写一个函数,每调用一次这个函数,就会将 num 的值增加1


//代码思路
//通过传址调用,在调用函数时让num++
#include
void add_num(int* num)
{
  (*num)++;
  return *num;
}
int main()
{
  int num = 0;
  add_num(&num);
  printf("%d\n", num);
  add_num(&num);
  printf("%d\n", num);
  add_num(&num);
  printf("%d\n", num);
  return 0;
}

输出结果:


输出:
1
2
3

5. 函数的嵌套调用和链式访问、

函数与函数之间是可以互相调用的。


5.1 嵌套调用

例:


#include 
void print()
{
    printf("hehe\n");
}
void circulate()
{
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        print();//函数circulate调用函数print
    }           //这个就是嵌套调用
}
int main()
{
    circulate();
    return 0;
}


输出结果:


输出:
hehe
hehe
hehe

注:函数可以嵌套调用但是不能嵌套定义。


5.2 链式访问

例:

#include
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    return 0;     //这就是一种链式访问
}


/

你们可以猜猜输出的结果是什么?


输出结果:


输出:4321

为什么会出现这种情况呢?


其实,在不同的编译器会有不同的结果,在VS2019这个编译器中


printf函数的返回值是打印在屏幕上的字符的个数。


6. 函数的声明和定义

6.1 函数声明:

1. 函数的声明就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。


2. 函数的声明一般出现在函数的使用之前,函数需要先声明再使用。


3. 函数的声明一般放在头文件。


例:

#include
int Add(int x, int y);//这是一个函数的声明
  //编译器自上而下扫描代码,如果函数的定义在调用之后,需要先声明
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  printf("%d\n", Add(a, b));//链式访问的应用
  return 0;
}
int Add(int x, int y)//一个简单的求和函数
{
  return x + y;



输出结果:


输入:3 5
输出:8

6.2 函数定义:

函数的定义是指函数的具体实现。


例:


#include
int Add(int x, int y)//函数的定义
{           //函数的定义也是一种特殊的声明
  return x + y;
}
int main()
{
  int a = 0;
  int b = 0;
  scanf("%d %d", &a, &b);
  printf("%d\n", Add(a, b));//链式访问的应用
  return 0;
}

输出结果:


输入:3 5
输出:8
拓展:


变量也遵循类似的规则:


int g_val;//变量的声明
int main()
{
  printf("g_val = %d\n", g_val);
  return 0;
}
int g_val = 2022;//变量的定义

输出结果:


输出:2022

以上是函数定义的基本规则,那函数定义与声明的应用场景是什么呢?



我们将主函数放在11_1.c源文件中。(这是我的测试模块)



引了头文件后我们就能正常使用函数了 。


将函数的实现放在add.c源文件中,函数定义放在add.h头文件中。(加法模块)



这样做有些什么好处呢?


1. 可以实现模块化开发,也就是分工:


在一个大的项目中,例如实现一个计算器,可以一个程序员写加法,另一个写减法,提高效率。


2. 可以实现代码的隐藏。


7. 函数递归

7.1 什么是递归

程序调用自身的编程技巧称为递归。(即函数自己调用自己)


递归能将一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,


只需少量的程序就可描述出解题过程所需要的多次重复计算,从而大大地减少程序的代码量,


递归的主要思考方式在于:把大事化小。


例:


#include
int main()
{
  printf("hehe");
  main();//main函数自己调用自己
  return 0;//这就是一种递归
}

输出结果:


死循环打印hehe

这是一种错误的递归。


这段代码打印到最后,程序会挂掉:



编译器提醒:Stack overflow(意思是:栈溢出)


那什么是栈溢出呢?


我曾在初识C语言中提到过内存的三个分区:



然后程序就挂了。


那怎样才能写出一个正确的递归呢?


7.2 递归的两个必要条件

1. 存在限制条件,当满足这个限制条件的时候,递归便不再继续。


2. 每次递归调用之后越来越接近这个限制条件。


我们通过练习来感受递归是怎么使用的。


7.2.1 练习1:

接受一个无符号整形,按照顺序打印它的每一位:


例如:输入:1234   打印:1 2 3 4


#include
void print(int n)
{
  if (n > 9)
  {
  print(n / 10);
  }
  printf("%d ", n % 10);
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  print(n);
  return 0;
}


输出结果:


输入:4257

输出:4 2 5 7

分析:



7.2.2 练习2:

编写函数求字符串的长度:(模拟实现strlen函数)


#include 
#include 
int my_strlen(char* str)
{
  int count = 0;
  while (*str != '\0')
  {
  count++;
  str++;//字符指针+1,向后跳1个字符
  }
  return count;
}
int main()
{
  char arr[] = "cat";
  //[c a t \0]
  //数组名其实是数组首元素的地址
  int len = my_strlen(arr);
  printf("%d\n", len);
  return 0;
}

输出结果:


输出:3

这个代码通过创建临时变量count,记录字符数。


如果不允许创建临时变量,又该怎么做呢?


#include
int my_strlen(char* str)
{
  if (*str != '\0')
  {
  return 1 + my_strlen(str + 1);//(str+1)使得下标每次调用右移一位
  }                         //每次调用函数也会使函数的返回值加一
  else
  {
  return 0;
  }
}
int main()
{
  char arr[] = "cat";
  int len = my_strlen(arr);
  printf("%d", len);
  return 0;
}


输出结果:


输出:3

其实这个代码的原理与练习1的是相同的,这就是函数递归的思考方式与应用。


7.3 递归与迭代

有些时候,我们遇到的问题可能递归并不好用了


7.3.1 练习3:

求n的阶乘:


#include
int fan(int n)
{
  if (n <= 1)
  {
  return 1;
  }
  else
  {
  return n * fan(n - 1);
  }
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  int ret = fan(n);
  printf("%d", ret);
  return 0;
}


输出结果:


输入:5

输出:120

(弊端)但是,如果要计算的值太大,函数调用太多,会导致栈溢出。


7.3.2 练习4:

求第n个斐波那契数:

#include
int fib(int n)
{
  if (n <= 2)
  {
  return 1;
  }
  else
  {
  return fib(n - 1) + fib(n - 2);
  }  //斐波那契数:1,1,2,3,5,8,13,21,34,55......
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  int ret = fib(n);
  printf("%d", ret);
  return 0;
}


输出结果:


输入:10

输出:55

输入:60

输出:      //程序一直在跑,输出结果迟迟未出

为什么会出现这样的结果呢?


因为使用递归来求效率太低了。  


因此,我们可以用迭代来求,其实迭代可以简单理解成除递归以外的方法(循环)


#include
int Fib(int n)
{
  int a = 1;
  int b = 1;
  int c = 1;
  while (n>2)//循环就是一种迭代
  {
  c = a + b;
  a = b;
  b = c;
  n--;
  }
  return c;
}
int main()
{
  int n = 0;
  scanf("%d", &n);
  int ret = Fib(n);
  printf("%d\n", ret);
  return 0;
}


输出结果:


输入:60

输出:1820529360

//这次一瞬间就打印出来了

总结:


1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。


2. 许多问题的迭代实现比递归实现效率更高,但是递归的形式更容易理解。


3. 如果问题太复杂,用迭代实现代码量太多,用递归实现会更简洁(补偿代码效率低的缺点)。


写在最后:

以上就是本篇文章的内容了,感谢你的阅读。


如果喜欢本文的话,欢迎点赞和评论,写下你的见解。


如果想和我一起学习编程,不妨点个关注,我们一起学习,一同成长。

相关文章
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3
|
1月前
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
20 2
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】回文数判定(头歌实践教学平台习题)【合集】
算术运算于 C 语言仿若精密 “齿轮组”,驱动着数值处理流程。编写函数求区间[100,500]中所有的回文数,要求每行打印10个数。根据提示在右侧编辑器Begin--End之间的区域内补充必要的代码。如果操作数是浮点数,在 C 语言中是不允许直接进行。的结果是 -1,因为 -7 除以 3 商为 -2,余数为 -1;注意:每一个数据输出格式为 printf("%4d", i);的结果是 1,因为 7 除以 -3 商为 -2,余数为 1。取余运算要求两个操作数必须是整数类型,包括。开始你的任务吧,祝你成功!
52 1
|
2月前
|
存储 C语言 开发者
【C语言】字符串操作函数详解
这些字符串操作函数在C语言中提供了强大的功能,帮助开发者有效地处理字符串数据。通过对每个函数的详细讲解、示例代码和表格说明,可以更好地理解如何使用这些函数进行各种字符串操作。如果在实际编程中遇到特定的字符串处理需求,可以参考这些函数和示例,灵活运用。
94 10
|
2月前
|
存储 程序员 C语言
【C语言】文件操作函数详解
C语言提供了一组标准库函数来处理文件操作,这些函数定义在 `<stdio.h>` 头文件中。文件操作包括文件的打开、读写、关闭以及文件属性的查询等。以下是常用文件操作函数的详细讲解,包括函数原型、参数说明、返回值说明、示例代码和表格汇总。
69 9
|
2月前
|
C语言 开发者
【C语言】数学函数详解
在C语言中,数学函数是由标准库 `math.h` 提供的。使用这些函数时,需要包含 `#include <math.h>` 头文件。以下是一些常用的数学函数的详细讲解,包括函数原型、参数说明、返回值说明以及示例代码和表格汇总。
64 6

热门文章

最新文章