【初阶C语言】认识和使用函数

1. 函数是什么
2. 库函数
3. 自定义函数
4. 函数参数
5. 函数调用
6. 函数的嵌套调用和链式访问
7. 函数的声明和定义
8. 函数递归

一、什么是函数

在数学中有函数,在C语言中也有函数,我们直接先给出一个定义:

在基维百科中函数被定义为子程序:

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

例如:main函数、printf打印函数等等,都是C语言中的函数。

我们可以把函数比作一个工厂,把东西送进去加工,出来就会变成你想要的产品。

 函数的基本构成:函数名、返回类型、函数参数和函数体


在C语言中,函数有两大类,库函数和自定义函数,下面一一介绍。

二、库函数

1.来源:C语言的基础库中提供了一系列库函数

2.库函数:

         将函数比作加工厂,那么库函数就是已经搭建好的有专属名字的工厂。我们只需要把原材料(数据)送入该工厂,然后接收产品(结果)即可。我们举个例子:

  • 计算字符串的长度:
char arr[]="love";//用数组来存放字符串
#include<stdio.h>
#include<string.h>
int main()
{
  char arr[]="love";//定义的数组(原材料)
  int ret=0;//定义一个变量记录字符串的长度(接收产品)
  ret strlen(arr);//送入工厂
  printf("%d\n",ret);//printf也是库函数
  return 0;
}

我们很清楚的看到,只需要把数组名字arr放入strlen函数中,再用变量ret接收产品就可以。

  • 数组名就是首元素的地址,通过首地址就可以找到整个字符串的内容,所以只需要把arr送入函数里面就可以。

注:
    但是库函数必须知道的一个秘密就是:使用库函数,必须包含 #include 对应的头文件。所以为什么会有#include<stdio.h>(main函数、printf函数等函数需要包含的)、#include<string.h>(strlen函数需要包含)这些东西。下面我们通过库函数的分类大概确定每种库函数需要使用头文件的类别

3.库函数分类

     一系列功能相近的函数

  • IO函数

        输入输出函数---<stdio.h>

  • 字符串操作函数

         strlen strcpy

  • 字符操作函数

        判断字母大小写、转换等等

  • 内存操作函数
  • 时间/日期函数
  • 数学函数

        <math.h>,如:Pow

  • 其他库函数

4.库函数举例 

     pow函数:用来计算次方,如:2^3=8

函数原型

double pow (double base, double exponent);//函数原型

 参数类型:该函数可以计算小数的幂,精度更高,所以也可以计算整形的变量。所以得出来的结果最高可以为小数,所以返回值是double类型。

函数参数:因为可以计算小数,所以参数的类型也可以是double类型。

第一个参数(double base)是底数,第二个参数(double exponent)是幂。

所以我们只需要把原材料(要计算的数)送到函数里面(工厂)就可以得出结果(产品)

 用法:我们现在要计算2^3的结果,只需要送进函数里面就行。

#include<tsdio.h>
#include<math.h>//pow函数需要包含的头文件
int main()
{
  int n=0;//创建一个变量用于接收计算的结果
  //n=pow(2,3);这样也可以,不过编译器会有警告
  n=(int)pow(2,3);
  printf("%d\n",n);
  return 0;
}

(int)为强制转换类型,转换为整形

总结:pow是一个可以计算次方的库函数,需要的头文件是#include<math,h>

strcpy函数:把A中的字符串复制到B中

函数原型:

char * strcpy ( char * destination, const char * source );//函数原型

 返回值类型:返回目标的地址,所以需要用指针接收,又因为是字符串操作函数,所以char

函数参数:第一个参数(destination)是目的地指针(收获地址),就是被复制进去的

                  第二个参数(source)是源头指针(发货的地方),要复制的值

注意:复制的时候会自带一个\0(字符串结束的标志),但是原数组的\0后面的内容还存在,只是不打印。

用法:

#include<stdio.h>

int main()
{
	char A[] = "#############";
	char B[] = "love you";
	printf("交换前A:%s\n", A);
	printf("交换前B:%s\n", A);
	strcpy(A, B);//过程是从B到A
	printf("交换后B:%s\n",A);
	return 0;
}

 因为数组名就是首地址,所以不需要传地址。

总结:strcpy是字符串复制函数,第一个参数是被复制参数,第二个是复制参数

menset函数:初始化函数,用于内存设置

void * memset ( void * ptr, int value, size_t num );//函数原型

 函数参数:第一个参数(ptr)是操作目标;第二个参数(value)是初始化的内容,第三个参数(num)是操作数目

用法:

#include<stdio.h>
int main()
{
	char arr[] = "love you";//原数组
	printf("%s\n", arr);
	memset(arr,'6',3);操作之后
	printf("%s\n",arr);
	return 0;
}

三、自定义函数

前言:将函数比如工厂,库函数就是官方已经制作好的工厂,那么自定义函数则需要我们自己去构造工厂然后自己使用,因为工厂还没有制作好,所以名字我们可以自己起。

1.自定义函数的组成

函数名字+返回值类型+函数参数+函数体

对比库函数来看,自定义函数比库函数多了函数体部分,该部分就是工厂的功能,需要我们自己实现。

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

这里的语句项就是函数体

2.自定义函数举例

(1)写一个函数实现两个数找最大值(传值)

#include <stdio.h>
//get_max函数的设计
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);
}

(2)写一个函数把两个整数的值交换 

我们先看错误的写法

 原因:把num1和num2的值传给形参x和y,所以x和y只是复制了一份数据。我们在复印件上面修改数据,是不会影响原件的。

我们看正确写法:

#include <stdio.h>
//正确的版本
void Swap2(int* px, int* py)//接收实参的时候,形参是指针变量
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;
}
int main()
{
	int num1 = 1;
	int num2 = 2;
	Swap2(&num1, &num2);//注意这里是&num1,&num2
	printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
	return 0;
}

结果:

 原因:把变量num1和num2的地址传递给形参,形参通过地址就可以找到源头,进而直接交换成功。这个称为传址调用

        可以这么理解1:我们把户口的网址传递给别人,别人可以通过登录该网址找到我们的户口,而直接修改;然后是我们把户口复印件给别人,别人在复印件上面修改对我们的户口是没有影响的。

       实质理解2:通过变量的地址可以找到该变量在内存中存放的位置,该位置就是变量数据所在地,直接在所在的修改数据从而达到修改的效果。

四、函数参数和函数调用

1.函数参数:实际参数(实参)和形似参数(形参)

实际参数:

   真实传递给函数的参数,叫实参。可以是变量、表达式、常量和函数,但是必须有确定的值。

举例:

Max(520,num1)//520是常量,num1是变量
Max(3+5,&num1)//3+5是表达式,&num1是变量
Max(printf("%d",num1))//函数形式

形式参数:

    用来接收实参。是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元)。只存在于函数中,出函数范围自动销毁。

举例:

   

2.实参和形参的内存

注:实参和形参所在的内存单元不一样,各自独立 

   我们用代码配图说明:

我们在编译器上面查看: 所以,形参是在函数体中创造的,只存在于函数中,出了函数自动销毁

3.函数调用

     传值调用和传址调用

  • 传值调用:函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
  • 传址调用:传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

传值调用:

#include <stdio.h>
//get_max函数的设计
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);
}

传址调用:

#include <stdio.h>
//正确的版本
void Swap2(int* px, int* py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;

	*py = tmp;
}
int main()
{
	int num1 = 1;
	int num2 = 2;
	Swap2(&num1, &num2);//传址调用
	printf("Swap2::num1 = %d num2 = %d\n", num1, num2);
	return 0;
}

五、函数链式访问和嵌套使用

1.嵌套调用

#include <stdio.h>
void new_line()
{
   printf("hehe\n");//在new_line函数内部又调用了printf函数
                    //称为嵌套调用
}
void three_line()
{
   int i = 0;
   for(i=0; i<3; i++)
   {
    new_line();//在three_line函数内又嵌套调用new_line
   }
}
int main()
{
    three_line();
    return 0;
}

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

void test()
{
  //int Add(int x,int y);//在函数内部嵌套定义
   Add(x,y)//这样子就可以
}
int main()
{
   test();
   return 0;
}

2.链式访问

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

#include <stdio.h>
#include <string.h>
int main()
{
   char arr[20] = "hello";
   int ret = strlen(strcat(arr,"bit"));//strcat的返回值作为strlen的参数
   printf("%d\n", ret);
   return 0;
}
#include <stdio.h>
int main()
{
   printf("%d", printf("%d", printf("%d", 43)));//结果4321
   //注:printf函数的返回值是打印在屏幕上字符的个数,打印几个,返回几
return 0;
}

六、函数的声明和定义

1.函数声明

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

举例:正常写法


#include<stdio.h>
int Add(int x,int y)//该函数的位置在mian函数前面
{
	return x + y;
}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d%d",&a,&b);
	int sum = Add(a, b);
	printf("%d\n",sum);
}

警告编译写法:

原因:因为编译器自上往下阅读,当调用Add函数的时候,前面没有遇到过Add函数,所以会有警告,当运行到后面才发现该函数。 

正确做法:在调用该函数前面先声明

int Add(int x,int y);//声明函数写法

 2.函数定义

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

注:函数的定义是一种特殊的声明。

举例:

int Add(int x, int y)
{
	return x + y;
}

这一整部分就是函数的定义

七、函数递归

前言:函数递归是函数部分较难点,用较少的代码表示庞大的过程。

1.我们看官方的定义;

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

总结:自己调用自己,把大事化小。
 

2.递归的两个条件

   因为递归的思想是先把大块的事情不断分成小块的事情,当分到不能再分的时候(条件1),开始从最小块的事情往大块的事情执行,没执行一次,就会更加接近这个条件(条件2)

官方:

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

3.举例

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

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

我们先给一个知识点:一个整数n/10=去掉个位剩下的数,如:1234/10=123

一个整数n%10=个位,如:123%10=3

分析:我们发现,要求打印1 2 3 4,不能使用常规方法;如果是打印4 3 2 1的话,就可以写一个循环,对1234连续取模(%)4次就可以了,但是现在顺序反了不可以。

要打印1,需要1%10=1,打印2,12%10=2,打印3,123%10=3,打印4,1234%10=4

我们可以发现:输入1234,需要拆成1,12,123和1234从小的开始计算。

   换个思想就是:打印1 2 3 4就可以拆成这样子:打印(1 2 3)和4,然后打印1 2 3又可以拆成打印(1 2)和3,打印1 2可以拆成打印1和2;1此时不能再拆,这就是限制条件1。很显然,这种思想就是递归的思想。

思路图:

 代码实现:

#include <stdio.h>
void print(int n)
{
   if(n>9)//条件1
   {
     print(n/10);//不断调用自身
   }
   printf("%d ", n%10);//条件2
}
int main()
{
   int num = 1234;
   print(num);
   return 0;
}

解析:只要满足n>9的条件就不断调用,直到不满足便开始打印。

代码图解:

 显而易见:递归把大事化小,用最短的代码表示最复杂的过程


函数递归是一个重难点,需要加大练习。后续会持续补充递归的内容;有时候递归太复杂,可以拆成非递归,也就是迭代。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Datawhale夏令营】任务二学习笔记

目录 一&#xff1a;python语法回顾 1.1 print() 1.2 列表与字典 1.3自定义函数与return 1.4火车类&#xff08;面向对象&#xff09; 实例化总结&#xff1a; 二&#xff1a;LightGBM 代码精读 2.1导入库 2.2数据准备与参数设置 2.3时间特征函数 2.4优化 2.5训练与…

Microsoft todo 数据导出

文章目录 官方说明&#xff1a; https://support.microsoft.com/zh-cn/office/导出您的-microsoft-待办事项帐户-d286b243-affb-4db4-addc-162e16588943 由于 微软待办 会自动与 Outlook 中的任务同步&#xff0c;因此您可以从 Outlook 中导出所有列表和任务。 若要导出列表和…

类加载机制,类加载顺序

类加载顺序 ①类加载从上往下执行&#xff0c;依次执行静态的初始化语句和初始化块&#xff0c;而且类加载优先于对象创建。&#xff08;静态初始化语句和初始化块只加载一次&#xff09; ②创建本类的对象时&#xff0c;从上往下执行一次非静态的初始化语句和初始化块&#…

企业服务器数据库被360后缀勒索病毒攻击后采取的措施

近期&#xff0c;360后缀勒索病毒的攻击事件频发&#xff0c;造成很多企业的服务器数据库遭受严重损失。360后缀勒索病毒是Beijingcrypt勒索家族中的一种病毒&#xff0c;该病毒的加密形式较为复杂&#xff0c;目前网络上没有解密工具&#xff0c;只有通过专业的技术人员对其进…

LinuxC语言-网络通信tcp/ip errno获取错误描述字符串

目录 服务端代码&#xff1a; 获取errno错误码&#xff1a; 客户端代码&#xff1a; 运行结果: 服务端代码&#xff1a; #include <stdio.h> #include<sys/types.h> #include<sys/socket.h> #include<string.h> #include<netinet/in.h> #in…

2022.09.17【读书笔记】丨生物信息学与功能基因组学(第十三章 蛋白质结构预测 下)

目录 蛋白质结构预测三种方法同源建模(比较建模)穿线法从头预测&#xff08;ab initio&#xff09;基于假设推荐策略 精度与方法选择Alphafold2相关信息 蛋白质结构预测 三种方法 同源建模(比较建模) 建模4步骤 1.模板选择和确定折叠构象 通过blast或delta-blast搜索同源蛋白…

【spring】spring bean的生命周期

spring bean的生命周期 文章目录 spring bean的生命周期简介一、bean的创建阶段二、bean的初始化阶段三、bean的销毁阶段四、spring bean的生命周期总述 简介 本文测试并且介绍了spring中bean的生命周期&#xff0c;如果只想知道结果可以跳到最后一部分直接查看。 一、bean的…

centos7搭建k8s环境并部署springboot项目

之前看了很多文章&#xff0c;都是部署后一直报错&#xff0c;百度解决后下次又忘了&#xff0c;这次决定把从头到尾的过程记录下来方便下次再看&#xff0c;部署参考文章尚硅谷Kubernetes&#xff08;k8s&#xff09;视频学习笔记_尚硅谷k8s笔记_溯光旅者的博客-CSDN博客 1、…

ELK报错no handler found for uri and method [PUT] 原因

执行后提示no handler found for uri and method post&#xff0c;最新版8.2的问题&#xff1f; 原因&#xff1a; index.mapping.single_type: true在索引上 设置将启用按索引的单一类型行为&#xff0c;该行为将在6.0后强制执行。 原 {type} 要改为 _doc&#xff0c;格式如…

MySQL运维:从全备sql文件中提取指定表的数据并恢复

目录 一、运行环境 二、需求说明 三、思路分析 五、具体方案 六、恢复表数据 一、运行环境 系统&#xff1a;CentOS7.3 数据库&#xff1a;MySQL 8.0.21 二、需求说明 线上有个表的数据被误操作了很多&#xff0c;无法通过bin-log进行具体的恢复。所以当前我们需要从全…

Redission分布式锁详解

前言 ​ 在分布式系统中&#xff0c;当不同进程或线程一起访问共享资源时&#xff0c;会造成资源争抢&#xff0c;如果不加以控制的话&#xff0c;就会引发程序错乱。而分布式锁它采用了一种互斥机制来防止线程或进程间相互干扰&#xff0c;从而保证了数据的一致性。 常见的分…

MFC第二十一天 CS架构多页面开发与数据交互、CImageList图像列表介绍 、CListCtrl-SetItem设置列表项的方法

文章目录 CImageList图像列表介绍CListCtrl图标的原理CListCtrl列表图标设置CListCtrl-SetItem设置列表项的方法 CS架构多页面开发与数据交互添加用户实现向导多页数据交互pch.hCLientXq.h CAppCPage1.hCPage1.cppCPage2.hCPage2.cppCWorkerDlg .hCWorkerDlg.cpp 多页数据修改C…

FRR+VPP

安装 三者的结合&#xff0c;实际上编译安装好就行了&#xff0c;不需要做任何代码上的修改&#xff0c;只需要安装和配置&#xff0c;然后你就有了一台路由器。 FRRouting使用frr-8.5.2版本&#xff0c;VPP使用23.06版本&#xff0c;DPDK和lcpng是VPP的插件&#xff0c;安装…

Spring Boot 应用程序生命周期扩展点妙用

文章目录 前言1. 应用程序生命周期扩展点2. 使用场景示例2.1 SpringApplicationRunListener2.2 ApplicationEnvironmentPreparedEvent2.3 ApplicationPreparedEvent2.4 ApplicationStartedEvent2.5 ApplicationReadyEvent2.6 ApplicationFailedEvent2.7 ApplicationRunner 3. 参…

Linux查看内存的几种方法

PS的拼接方法 ps aux|head -1;ps aux|grep -v PID|sort -rn -k 4|head 进程的 status 比如说你要查看的进程pid是33123 cat /proc/33123/status VmRSS: 表示占用的物理内存 top PID&#xff1a;进程的ID USER&#xff1a;进程所有者 PR&#xff1a;进程的优先级别&#x…

Vue2基础一、快速入门

零、文章目录 Vue2基础一、快速入门 1、Vue 概念 &#xff08;1&#xff09;为什么学 前端必备技能 岗位多&#xff0c;绝大互联网公司都在使用Vue 提高开发效率 高薪必备技能&#xff08;Vue2Vue3&#xff09; &#xff08;2&#xff09;Vue是什么 **概念&#xff1a;…

ARP协议(地址解析协议)详解

ARP协议&#xff08;地址解析协议&#xff09;详解 ARP协议的作用映射方式静态映射动态映射 ARP原理及流程ARP请求ARP响应 ARP协议报文首部 ARP协议的作用 ARP协议是“Address Resolution Protocol”&#xff08;地址解析协议&#xff09;的缩写。其作用是在以太网环境中&…

【李宏毅 DLHLP 深度学习人类语言处理 HW1】

李宏毅 DLHLP 深度学习人类语言处理 HW1 相关资料HW1 语音小白在网上没有找到这门课的作业分享&#xff0c;那就记录一下自己的作业吧。 相关资料 课程官网&#xff1a;https://speech.ee.ntu.edu.tw/~hylee/dlhlp/2020-spring.php 作业github代码1&#xff1a;https://githu…

给jupter设置新环境

文章目录 给jupternotebook设置新环境遇到的报错添加路径的方法 给jupternotebook设置新环境 # 先在anaconda界面新建环境 conda env list # 查看conda prompt下的有的环境变量 带星号的是当前活跃的 activate XXXX pip install ipykernel ipython ipython kernel install --u…

【机器学习】西瓜书学习心得及课后习题参考答案—第3章线性模型

过了一遍第三章&#xff0c;大致理解了内容&#xff0c;认识了线性回归模型&#xff0c;对数几率回归模型&#xff0c;线性判别分析方法&#xff0c;以及多分类学习&#xff0c;其中有很多数学推理过程以参考他人现有思想为主&#xff0c;没有亲手去推。 术语学习 线性模型 l…
最新文章