第三节 函数

news2024/11/17 6:01:11

第三节 函数



目录

  • 一. 函数是什么?
  • 二. C语言中函数的分类
    • 1. 库函数:
    • 2. 自定义函数
  • 三. 函数的参数
    • 1. 实际参数(实参):
    • 2. 形式参数(形参):
  • 四. 函数的调用
    • 1. 传值调用
    • 2. 传址调用
    • 3. 练习
  • 五. 函数的嵌套调用和链式访问
    • 1. 嵌套调用
    • 2. 链式访问
  • 六. 函数的声明和定义
    • 1. 函数声明
    • 2. 函数定义
  • 七. 函数递归
    • 1. 什么是递归?
    • 2. 递归的两个必要条件
    • 3. 递归与迭代



本章重点:
函数是什么
库函数
自定义函数
函数参数
函数调用
函数的嵌套调用和链式访问
函数的声明和定义
函数递归


一. 函数是什么?

数学中我们常见到函数的概念。但是你了解C语言中的函数吗?

维基百科中对函数的定义:子程序
  在计算机科学中,子程序(英语:Subroutine, procedure, function, routine, method, subprogram, callable unit),是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代 码,具备相对的独立性。
  一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。


二. C语言中函数的分类

库函数
自定义函数


1. 库函数:

为什么会有库函数?
  我们知道在我们学习C语言编程的时候,总是在一个代码编写完成之后迫不及待的想知道结果,想把这个结果打印到我们的屏幕上看看。这个时候我们会频繁的使用一个功能:将信息按照一定的格式打印到屏幕上(printf)。
  在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。
  在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员进行软件开发。

简单的总结,C语言常用的库函数都有:
IO函数
字符串操作函数
字符操作函数
内存操作函数
时间/日期函数
数学函数
其他库函数


例子如下:

strcpy

char * strcpy ( char * destination, const char * source );

memset

void * memset ( void * ptr, int value, size_t num );


注意:使用库函数,必须包含 #include 对应的头文件。


2. 自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。
但是不一样的是这些都是我们自己来设计。这给程序员一个很大的发挥空间。

函数的组成:

ret_type fun_name(para1, * )
{
 statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1    函数参数


例子如下:
写一个函数可以找出两个整数中的最大值:

#include <stdio.h>

int get_max(int x, int y)
{
	return (x > y) ? (x) : (y);
}
int main()
{
	int num1 = 10;
	int num2 = 20;
	int max = get_max(num1, num2);
	printf("max = %d\n", max);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


写一个函数可以交换两个整形变量的内容:

#include <stdio.h>
//实现成函数,但是不能完成任务
void Swap1(int x, int y)
{
	int tmp = 0;
	tmp = x;
	x = y;
	y = tmp;
}
//正确的版本
void Swap2(int* px, int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int num1 = 1;
	int num2 = 2;
	Swap1(num1, num2);
	printf("Swap1::num1 = %d num2 = %d\n", num1, num2);
	Swap2(&num1, &num2);
	printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


函数调用的时候,将实参传递给形参,
形参其实是实参的一份临时拷贝,
对形参的修改,不会改变实参。


三. 函数的参数

1. 实际参数(实参):

真实传给函数的参数,叫实参。
实参可以是:常量、变量、表达式、函数等。
无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。


2. 形式参数(形参):

  形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效。
  上面 Swap1 和 Swap2 函数中的参数 x,y,px,py 都是形式参数。在main函数中传给 Swap1 的 num1 ,num2 和传给 Swap2 函数的 &num1 , &num2 是实际参数。


这里我们对函数的实参和形参进行分析:

在这里插入图片描述


代码对应的内存分配如下:

在这里插入图片描述


  这里可以看到 Swap1 函数在调用的时候, x , y 拥有自己的空间,同时拥有了和实参一模一样的内容。
  所以我们可以简单的认为:形参实例化之后其实相当于实参的一份临时拷贝。


四. 函数的调用

1. 传值调用

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


2. 传址调用

  传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
  这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。


3. 练习

写一个函数可以判断一个数是不是素数。
打印100~200之间的素数

#include<stdio.h>
#include<stdbool.h>
#include<math.h>

bool is_prime(int n)
{
	//拿2~sqrt(n)之间数字试除
	int j = 0;
	for (j = 2; j <= sqrt(n); j++)
	{
		if (n % j == 0)
			return false;
	}
	return true;//是素数
}

int main()
{
	//打印100~200之间的素数
	int i = 0;
	int count = 0;
	for (i = 101; i <= 200; i+=2)
	{
		//判断i是否是素数?
		if (is_prime(i))
		{
			count++;
			printf("%d ", i);
		}
	}
	printf("\ncount = %d\n", count);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


写一个函数判断一年是不是闰年。
打印1000~2000年之间的闰年,使用闰年判断函数:

#include<stdio.h>
#include<stdbool.h>

bool is_leap_year(int y)
{
	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
		return true;
	else
		return false;
}


int main()
{
	int y = 0;
	int count = 0;
	for (y = 1000; y <= 2000; y++)
	{
		//判断y是否是闰年
		if (is_leap_year(y))
		{
			count++;
			printf("%d ", y);
		}
	}
	printf("\ncount = %d\n", count);

	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


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

#include<stdio.h>

int binary_search(int arr[], int k, int sz)
{
	int left = 0;
	int right = sz - 1;

	while (left<=right)
	{
		//int mid = (left + right) / 2;
		int mid = left + (right - left) / 2;

		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			return mid;
		}
	}
	return -1;
}

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	scanf("%d", &k);//输入的是要查找的值
	//findnum();
	int sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary_search(arr, k, sz);
	if (ret == -1)
	{
		printf("找不到\n");
	}
	else
	{
		printf("找到了,下标是:%d\n", ret);
	}
	return 0;
}


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

#include<stdio.h>

void Add(int* p)
{
	*p = *p + 1;
}

int main()
{
	int num = 0;
	Add(&num);
	printf("%d\n", num);
	Add(&num);
	printf("%d\n", num);
	Add(&num);
	printf("%d\n", num);

	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


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

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

1. 嵌套调用

例子如下:

#include <stdio.h>
void new_line()
{
 printf("hehe\n");
}
void three_line()
{
    int i = 0;
 for(i=0; i<3; i++)
   {
        new_line();
   }
}
int main()
{
 three_line();
 return 0;
}

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


2. 链式访问

把一个函数的返回值作为另外一个函数的参数。

例子如下:

#include <stdio.h>
#include <string.h>
int main()
{
    char arr[20] = "hello";
    int ret = strlen(strcat(arr, "bit"));
    printf("%d\n", ret);
    return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


小题一道:

#include <stdio.h>
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数
    return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


六. 函数的声明和定义

1. 函数声明

告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
函数的声明一般出现在函数的使用之前。要满足先声明后使用。
函数的声明一般要放在头文件中的。


2. 函数定义

函数的定义是指函数的具体实现,交待函数的功能实现。


七. 函数递归

1. 什么是递归?

  程序调用自身的编程技巧称为递归( recursion)。
  递归=递推+回归
  递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的
  一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略
  只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

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


2. 递归的两个必要条件

存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。

练习:

接受一个整型值(无符号),按照顺序打印它的每一位。

例如:
输入:1234,输出 1 2 3 4

代码如下:

#include<stdio.h>

void Print(int n)
{
	if (n > 9)
	{
		Print(n/10);
	}
	printf("%d ", n % 10);
}

int main()
{
	int num = 0;
	scanf("%d", &num);
	Print(num);
	return 0;
}

运行代码成功,结果如下:

在这里插入图片描述

在这里插入图片描述

注意:关于函数栈帧的创建与销毁详见博客:https://danbaku.blog.csdn.net/article/details/132570659


编写函数不允许创建临时变量,求字符串的长度

代码如下:

#include<stdio.h>

size_t my_strlen(char* str)
{
	if (*str == '\0')
		return 0;
	else
		return 1 + my_strlen(str+1);
}

int main()
{
	char arr[] = "abc";
	size_t len = my_strlen(arr);//传递的是数组首元素的地址
	printf("%zd\n", len);

	return 0;
}

代码运行成功,结果如下:
在这里插入图片描述


注意:
size_t 是一种类型,是无符号整型的
size_t 就是为sizeof设计的
size_t 类型的数据打印的时候使用%zd
(有些编译器版本好像无法识别%zd,那就不要用%zd了)


此处贴张图助于理解:

在这里插入图片描述


3. 递归与迭代

练习:

求n的阶乘。(不考虑溢出)

#include<stdio.h>

int Fac(int n)
{
	if (n <= 1)
		return 1;
	else
		return n* Fac(n - 1);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int r = Fac(n);
	printf("%d\n", r);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


求第n个斐波那契数。(不考虑溢出)

#include<stdio.h>

int Fib(int n)
{
	if (n <= 2)
		return 1;
	else
		return Fib(n - 1) + Fib(n - 2);
}

int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fib(n);
	printf("%d\n", ret);
	return 0;
}

代码运行成功,结果如下:

在这里插入图片描述


但是我们发现有问题;
在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
使用 fac 函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。


为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。


那我们如何改进呢?
在调试 factorial 函数的时候,如果你的参数比较大,那就会报错: stack overflow(栈溢出)这样的信息。
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。


那如何解决上述的问题:
将递归改写成非递归。
使用static对象替代 nonstatic 局部对象。在递归函数设计中,可以使用 static 对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放 nonstatic 对象的开销,而且 static 对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。


将代码进行修改,使用非递归方式:

#include<stdio.h>

int Fib(int n)
{
	int a = 1;
	int b = 1;
	int c = 1;
	while (n >= 3)
	{
		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;
}


提示:
  许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。



本篇博客为本人学习C语言时的详细笔记,如有错误之处,还望各位指正。
文章为原创,如要转载请注明出处

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/947197.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机视觉】YOLO 入门:训练 COCO128 数据集

一、COCO128 数据集 我们以最近大热的YOLOv8为例&#xff0c;回顾一下之前的安装过程&#xff1a; %pip install ultralytics import ultralytics ultralytics.checks()这里选择训练的数据集为&#xff1a;COCO128 COCO128是一个小型教程数据集&#xff0c;由COCOtrain2017中…

【pyqt5界面化工具开发-9】触发事件的绑定 信号-槽

目录 0x00 前言&#xff1a; 一、基础代码布局 二、添加逻辑代码 三、触发事件绑定逻辑代码 0x00 前言&#xff1a; 1.信号(signal) 事件(点击、关闭等状态发生改变的触发事件) 2.槽( slot) 捕获信号后--->执行相应的逻辑代码 3.信号-槽 链接 为实现&#xff1a;触发事件…

java-初识Servlet,Tomcat,JDBC

文章目录 前言一、ServletServlet 生命周期Servlet 实例Servlet 过滤器 二、TomcatJDBCJDBC连接数据库实例 总结 前言 java入门须知的重要概念/名词/技术 等 一、Servlet Servlet是Java Web开发中的一个核心组件&#xff0c;它是基于Java语言编写的服务器端程序&#xff0c;…

【CI/CD技术专题】「Docker实战系列」本地进行生成镜像以及标签Tag推送到DockerHub

背景介绍 Docker镜像构建成功后&#xff0c;只要有docker环境就可以使用&#xff0c;但必须将镜像推送到Docker Hub上去。创建的镜像最好要符合Docker Hub的tag要求&#xff0c;因为在Docker Hub注册的用户名是liboware&#xff0c;最后利用docker push命令推送镜像到公共仓库…

2023视觉SLAM的研究改进方向

1. 增加对动态场景的鲁棒性&#xff08;动态SLAM&#xff09; 传统的视觉SLAM算法通常假设场景是静态的&#xff0c;这种假设对于动态场景是不适用的。在动态场景中&#xff0c;物体的位置和姿态会发生变化&#xff0c;这会对视觉SLAM算法的精度和鲁棒性造成很大的影响。因此&…

YOLOv5、YOLOv8改进:BoTNet Transformer

目录 1.简介 2.YOLOv5改进 2.1增加以下yolov5s_botnet.yaml文件 2.2common.py配置 2.3 yolo.py配置修改 1.简介 论文地址 Paper 本文提出的BoTNet是一种简单高效的网络&#xff0c;有效的将SA应用到多种视觉任务&#xff0c;如图像识别、目标检测、实例分割任务。通过将R…

(十九)大数据实战——Flume数据采集框架安装部署

前言 本节内容我们主要介绍一下大数据数据采集框架flume的安装部署&#xff0c;Flume 是一款流行的开源分布式系统&#xff0c;用于高效地采集、汇总和传输大规模数据。它主要用于处理大量产生的日志数据和事件流。Flume 支持从各种数据源&#xff08;如日志文件、消息队列、数…

【C/C++】虚析构 | 抽象类

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

【核磁共振成像】并行采集MRI

目录 一、并行成像二、SENSE重建三、SMASH重建四、灵敏度校准五、AUTO-SMASH和VD-AUTO-SMASH六、GRAPPA重建七、SPACE RIP重建算法八、PILS重建算法九、PRUNO重建算法十、UNFOLD算法 一、并行成像 并行MR成像(pMRI):相位阵列接受线圈不但各有自己专用的接受通道&#xff0c;而且…

ChatGPT⼊门到精通(2):ChatGPT 能为我们做什么

⼀、雇佣免费的⼲活⼩弟 有了ChatGPT后&#xff0c;就好⽐你有了好⼏个帮你免费打⼯的「⼩弟」&#xff0c;他们可以帮你做很多 ⼯作。我简单总结⼀些我⽬前使⽤过的⽐较好的基于ChatGPT的服务和应⽤。 1、总结、分析 当我们在阅读⼀些⽂章和新闻的时候&#xff0c;有的⽂章写…

Redis-监听过期key-JAVA实现方案

一、创建监听配置类 RedisListenerConfig。 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.d…

mysql与msql2数据驱动

mysql基本使用 数据库操作&#xff08;DDL&#xff09; -- 数据考操作 -- 1.查询所有数据库 SHOW DATABASES;-- 2.选择数据库 USE learn_mysql;-- 3.当前正在使用的数据库 SELECT DATABASE();-- 4.创建数据库 CREATE DATABASE IF NOT EXISTS learn_mysql;-- 5.删除数据库 DRO…

贸易行业:财务信息化流程优化解决方案

一、整体架构图 1.1 价值分析 SAP财务流程优化V2.0版本 ——建立跨部门协作的“信息流”桥梁 &#xff0c;优化SAP与异构系统之间的数据交换 &#xff0c;信息传递由人工整理到EXCEL 到 系统导出 标准化EXCEL &#xff0c;提升作业人员数据整理、搬运的工作&#xff0c;提高…

二级MySQL(十)——单表查询

这里我们只在一个表内查询&#xff0c;用到的是较为简单的SELECT函数形式 1、查询指定的字段&#xff1a; 用到的数据库是之前提到的S、P、SP数据库 S表格用到的总数据&#xff1a; 首先我们查询所有供应商的序号和名字 这时都是独立的&#xff0c;没有关系&#xff0c;我们找…

pm2部署nuxt3项目

pm2部署nuxt3项目 阅读时长&#xff1a;8分钟 本文内容&#xff1a;本文其实前面开发AI数字人网站的延续。 window上安装ubuntu虚拟机&#xff0c;并在虚拟机中使用pm2部署 Nuxt3 项目. Nuxt3Vitetypescriptpm2 1. 安装node环境 下载 # 进入node目录 cd /node # 下载 wget h…

input时间表单默认样式修改(input[type=“date“])

一、时间选择的种类: HTML代码&#xff1a; <input type"date" value"2018-11-15" />选择日期&#xff1a; 选择时间&#xff1a; <input type"time" value"22:52" />在这里插入图片描述 选择星期&#xff1a; <…

POSTGRESQL WAL 日志问题合集之WAL 如何解析

开头还是介绍一下群&#xff0c;如果感兴趣polardb ,mongodb ,mysql ,postgresql ,redis 等有问题&#xff0c;有需求都可以加群群内有各大数据库行业大咖&#xff0c;CTO&#xff0c;可以解决你的问题。加群请加 liuaustin3微信号 &#xff0c;在新加的朋友会分到3群 &#xf…

Python实战之数据表提取和下载自动化

在网络爬虫领域&#xff0c;动态渲染类型页面的数据提取和下载自动化是一个常见的挑战。本文将介绍如何利用Pyppeteer库完成这一任务&#xff0c;帮助您轻松地提取动态渲染页面中的数据表并实现下载自动化。 一、环境准备 首先&#xff0c;确保您已经安装了Python环境。接下来…

uniapp 支持图片放大

<view class"list" v-for"(item, index) in urls" :key"index"><image :src"item" click"viewImg(item, index)" disabled></image></view> js // 预览大图 viewImg(data, index) {uni.previewImag…

《游戏编程模式》学习笔记(九)游戏循环 Sequencing Patterns

定义 一个游戏循环会在游玩时不断运行。 每一次循环&#xff0c;它都会无阻塞地处理玩家的输入&#xff0c;更新游戏的状态&#xff0c;渲染游戏。它追踪时间的消耗并控制游戏的速度。游戏循环需要做到始终以固定的速度运行游戏。 一个游戏循环中通常包含处理输入部分&#xf…