C语言——初识函数

简介: 哈喽大家好,我是保护小周ღ,本期为大家带来的是C语言基础知识当中的函数,从什么是函数,库函数和自定义函数,函数的参数,函数的调用,函数的声明和定义,函数的递归几个方面保姆级讲述,包您一看就会,快来试试吧~

 image.gif编辑

哈喽大家好,我是保护小周ღ,本期为大家带来的是C语言基础知识当中的函数,从什么是函数库函数和自定义函数函数的参数函数的调用函数的声明和定义函数的递归几个方面保姆级讲述包您一看就会,快来试试吧~

image.gif编辑

目录

一、初识函数

1.1 什么是函数

二、函数的分类

2.1 库函数

2.1.1 库函数的意义是什么

2.2 自定义函数

2.3 求一个字符数组数据元素的个数

2.3.1 库函数实现

2.3.2 自定义函数实现

三、函数的参数

3.1 实际参数

3.2 形式参数

四、函数的调用

4.1 传值调用

4.2 传址调用

4.3 传值调调用,传址调用那个更好?

五、函数的声明和定义

六、函数的递归

6.1 递归简介

6.2 递归和非递归

6.2.1 求n的阶乘递归做法

6.2.2 求n的阶乘非递归做法


一、初识函数

1.1 什么是函数

维基百科中对函数的定义:子程序

1. C语言的函数是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性且可以重复使用,用来独立的完成某个功能。

2. 一般函数有参数有返回值,函数可以没有参数也可以没有返回值,C语言 main() 也是一个函数

函数可以没有参数也可以没有返回值,我们可以写成:

void 函数名( void )
{
   语句;
   ……
}

image.gif

image.gif编辑

image.gif编辑

为什么说头文件也可以说是一个函数集呢,举个简单的例子,我们的输入、输出函数,scanf、printf,都是函数且都在 stdio.h 头文件里,所以我们在使用这来两个函数是需要声明头文件,才可以使用,注意C 语言没有专门的输入、输出语句。printf、scanf,这个两个函数是有返回值的,返回值正常情况下是函数参数的个数。简单来说包含在头文件里的函数统称为库函数。


二、函数的分类

2.1 库函数

上文简短的了解了什么样的函数叫做库函数,是我们C语言的基础库中提供的一系列的函数,可以解决我们常用的基础功能,我们声明对应的头文件,即可直接使用库函数实现某一方面的功能,例如说,输入、输出、strlen,strcpy,sprt ,pow等。函数有一个特点,就是有一对标准的括号()这个括号其实是函数的操作符,有一个例外,sizeof() 这个是关键字,不是函数。

使用库函数,必须包含 #include 对应的头文件,再输入与之对应的参数,即可使用实现某一方面的功能。

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

IO函数

字符串操作函数

字符操作函数

内存操作函数

时间/日期函数

数学函数

……


2.1.1 库函数的意义是什么

C语言的库函数并不是C语言本身的一部分,它是由编译程序根据一般用户的需要编制并提供用户使用的一组程序,例如Vs 2019里面的库函数就是按照国际C语言的标准,统一参数,统一函数名,统一返回类型,统一函数功能,而函数的功能实现(代码实现)则需要要微软公司自主设计实现。C的库函数极大地方便了用户,同时也补充了C语言本身的不足。

C语言就是语法,“C语言标准”就是做了一些工作:

设定:库函数功能,函数名,参数,返回类型,怎么实现(不管),实现是交给编译器厂商实现。

如何查看这些库函数的标准呢?博主会把网站链接发在评论区,有兴趣的朋友可以看看。

image.gif编辑image.gif编辑


2.2 自定义函数

根据自己的需求,设计一个函数来实现某一方面的功能,自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是这一切都是由自身来设计的,有很大的发挥空间。

自定义函数的作用:是通过函数封装可重复使用的代码块,从而节省代码数。自定义函数指的是定义一个函数库里没有的函数,并给予其运行方式。将代码段封装成函数的过程叫做函数定义。

自定义函数的好处,我们把一个具体的功能用一个函数封装,如果有需要还可以把细小的功能继续封装为函数,这样在结构上是不是就很雅观,通俗易懂,而且函数是可以重复使用的,像交换两个整型变量的内容,我们在数据排序的时候是不是要经常使用啊,直接封装一个函数,在数据交换的时候直接调用,输入对应的参数,即可实现功能。

在程序设计中,使用的最多的就是自定义函数。


2.3 求一个字符数组数据元素的个数

首先我们使用的是库函数的strlen()函数计算,那国际标准下的strlen()函数 有什么格式上的要求呢?

image.gif编辑

以上标准,我们可以知道该函数的基本使用方法:

1. 函数的返回值 size_t 为整型数据,函数的参数是一个 const 修饰的字符指针,字符指针对应的值不可被修改,所以我们传参的时候可以是 一个字符数组的数组名,代表字符数组的首地址。给到指针后,可以通过指针来访问数组元素,当然字符数组的内容是不允许修改的,所以用const 修饰。当然这个传参也可以是指针,注意参数类型。

2. C字符串的长度是由结束空字符决定的:一个C字符串的长度是字符串开始和结束空字符之间的字符数(不包括结束空字符本身),字符串的结束标志为 ‘\0’,访问到这个标志程序结束,所计算结果即使字符串长度。

3. 不应将此值与保存字符串的数组的大小混淆。例如:

char mystr[100]="test string";
image.gif

定义了一个长度为100个字符的字符数组,但是初始化mystr时使用的C字符串长度只有11个字符。因此,当sizeof(mystr)计算值为100时,strlen(mystr)返回11。虽然开辟了很大的空间,并不代表这即使mystr 存储的字符串的长度,不是一个概念,严格根据结束标志来‘\0’。

所以库函数的内部实现还是有些复杂的,要求很多,但造就了标准。


2.3.1 库函数实现

image.gif编辑


2.3.2 自定义函数实现

我们就按照strlen()函数的标准,模拟实现一下函数的功能,看能不能达到相同的效果。

image.gif编辑

当然这个函数功能的实现部分有很多种方法,递归也可以实现,所以自定义函数的自由度比较高。

那我们可不可以自己写一个头文件,把我们自己写的函数包含到里面去,使用的时候声明自定义的头文件,直接使用函数呢?答案是可以的,以后参加工作写项目之后就可以领略其中的奥妙。


三、函数的参数

3.1 实际参数

1. 实际参数,简称实参,指在主调函数中调用一个函数时,函数名后括号中的参数。真实传给函数的参数,叫实参。

2. 实参可以是:常量、变量、表达式、函数等。

3. 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形

参。

3.2 形式参数

1. 形式参数,形参是在定义函数名和函数体时使用的参数,目的是用来接收调用函数时传递的参数。

2. 形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(操作系统为形参分配空间),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了,形参的作用域就在函数的内部。

image.gif编辑


四、函数的调用

4.1 传值调用

image.gif编辑

image.gif编辑

传值总结:我们在调用sum()函数的时候,操作系统会为A,B 在栈区上开辟空间,同时拥有了和实参a,b一样的内容,函数被调用时,形参可以理解为是实参的一份临时拷贝,函数结束,自动销毁,空间由操作系统回收。所占main() 函数结束之后我们整个程序就结束了。


4.2 传址调用

顾名思义,就是传地址调用,例如说,上图我们把&a,&b 的地址传过去,用指针接收,指针就有能力找到这两个变量的地址,并访问其中的内容,当然也有能力改变其中的内容,这个时候形参就不是实参的拷贝了。

image.gif编辑

image.gif编辑

void 就是空的意思,放在函数名前代表这个函数没有返回值。

如果函数没有写void ,且没有写返回值,函数默认返回一个值,有些编译器返回的是,最后一条语句产生的结果。例如: 函数最后 sum=A+B; 那么默认返回 sum;(要避免这种情况)


4.3 传值调调用,传址调用那个更好?

这两种方式都有属于自己的应用场景。

如果我们采用传值调用,实参传给形参,我们的形参也需要开辟和实参一样大的空间,例如:实参占20个字节,函数调用时,形参也会开辟20个字节的空间存储,需要准备一份额外的临时空间,这样会造成一定的空间上的浪费,从时间的角度来讲,传参也需要时间来接收的。但是,因为形参是一份独立的空间,所以形参的改变,不会对实参造成任何影响,我们的return 标识符 一次也只能返回一个值。这个应用场景结合实际来选择。

如果我们采用传址调用我们把实参的地址传给形参,&实参 的类型就是指针类型,所以我们形参要类型对齐,采用指针接收,指针有能力找到实参的地址,有能力访问或改变实参的值。且指针所占空间取决于实参的大小(非普通情况),对于数组之类的实参(一篇连续的空间),指针只需要存储数组首元素的地址,就可以访问整个数组元素,这个时候传值调用,冤大头,而且也无法对原数组进行操作。一个普通类型的指针在32位平台占4个字节,在64位平台占8个字节。


五、函数的声明和定义

1. 函数在使用之前一定要声明,告诉编译器,函数名叫什么,返回类型是什么,参数是什么等等。

2. 函数的要满足先声明后使用的同时,一般放在头文件的下面

被调函数放在主函数下面:

#include <stdio.h>intmain()
{
inta=10;
intb=20;
ints=0;
intSum(int,int);//函数的声明;//int Sum(int A,int B);s=Sum(a,b);
printf("%d",s);
return0;
}
intSum(intA, intB)
{
intsum=0;
sum=A+B;
returnsum;
}

image.gif

这个时候,我们就需要在mian() 函数之前声明一下被调函数,因为编译器扫描代码的时候是从前往后扫描,在执行mian( )函数时,执行被调函数,编译器在扫描的时候遇到了声明,所以呢他就继续往后找,直到找到了被调函数,被调函数结束,返回主函数继续执行。这个时候如果没有在main() 函数之前声明,编译器就视为函数未定义,报错。

被调函数放在主函数上面:

#include <stdio.h>intSum(intA, intB)
{
intsum=0;
sum=A+B;
returnsum;
}
intmain()
{
inta=10;
intb=20;
ints=0;
s=Sum(a,b);
printf("%d",s);
return0;
}

image.gif

还是一样的道理,编译器顺序扫描,扫描的时候就已经知道了有这个被调函数,在主函数执行被调函数的时候,编译器就可以找的到被调函数的位置,就不会报错了。


六、函数的递归

6.1 递归简介

什么是递归呢? 简单来讲就是函数自己调用自己的编程技巧,只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量,递归可以把大型复杂的问题层层转化未一个与原问题相似的类型较小的问题来求解,我们在学习数据结构的时候会大量运用。

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

递归也有缺点,就是空间复杂度大,函数调用函数再继续调用函数,每调用一次,会在栈区上开辟一块空间来维护,函数只有在结束的时候才会回收空间,直到递归结束前,是不是开辟了很多空间来维护,递归结束后,函数陆续回收。同一时间的空间复杂度就挺高的。

递归的两个必要条件:

1. 存在终止条件,当满足这个终止条件的时候,递归就不再继续。

2. 每次递归调用之后越来越接近这个限制条件(循环也是相同的道理)。

系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一

直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。


6.2 递归和非递归

6.2.1 求n的阶乘递归做法

image.gif编辑

画图分析:

image.gif编辑

递归的缺陷:如果栈帧深度太深(递归的次数多),栈空间不够用(大概只有几兆)可能会溢出。


6.2.2 求n的阶乘非递归做法

image.gif编辑

那就是循环啦,准确的来讲是叫做迭代。

递归改非递归的方法:

    1. 直接改循环(简单一点的递归)
    2. 借助数据结构的“栈”模拟递归过程(复杂一点的递归)

    至此C语言基本函数的知识博主已经分享完了,相信大家对这个函数的概念、使用有了一定的理解,大家可以自己动手敲敲代码,感受一下。

    image.gif编辑

    本期收录于博主的专栏——C语言,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“C语言基础知识”。C语言_保护小周ღ的博客-CSDN博客

    感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ  *★,°*:.☆( ̄▽ ̄)/$:*.°★*  

    下一篇:函数栈帧的创建和销毁

    文章多处存在借鉴,如有侵权请联系修改删除!image.gif编辑

    相关文章
    |
    16天前
    |
    存储 Serverless C语言
    【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
    本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。
    |
    19天前
    |
    存储 C语言
    C语言程序设计核心详解 第十章:位运算和c语言文件操作详解_文件操作函数
    本文详细介绍了C语言中的位运算和文件操作。位运算包括按位与、或、异或、取反、左移和右移等六种运算符及其复合赋值运算符,每种运算符的功能和应用场景都有具体说明。文件操作部分则涵盖了文件的概念、分类、文件类型指针、文件的打开与关闭、读写操作及当前读写位置的调整等内容,提供了丰富的示例帮助理解。通过对本文的学习,读者可以全面掌握C语言中的位运算和文件处理技术。
    |
    19天前
    |
    存储 C语言
    C语言程序设计核心详解 第七章 函数和预编译命令
    本章介绍C语言中的函数定义与使用,以及预编译命令。主要内容包括函数的定义格式、调用方式和示例分析。C程序结构分为`main()`单框架或多子函数框架。函数不能嵌套定义但可互相调用。变量具有类型、作用范围和存储类别三种属性,其中作用范围分为局部和全局。预编译命令包括文件包含和宏定义,宏定义分为无参和带参两种形式。此外,还介绍了变量的存储类别及其特点。通过实例详细解析了函数调用过程及宏定义的应用。
    |
    24天前
    |
    Linux C语言
    C语言 多进程编程(三)信号处理方式和自定义处理函数
    本文详细介绍了Linux系统中进程间通信的关键机制——信号。首先解释了信号作为一种异步通知机制的特点及其主要来源,接着列举了常见的信号类型及其定义。文章进一步探讨了信号的处理流程和Linux中处理信号的方式,包括忽略信号、捕捉信号以及执行默认操作。此外,通过具体示例演示了如何创建子进程并通过信号进行控制。最后,讲解了如何通过`signal`函数自定义信号处理函数,并提供了完整的示例代码,展示了父子进程之间通过信号进行通信的过程。
    |
    24天前
    |
    C语言
    C语言 字符串操作函数
    本文档详细介绍了多个常用的字符串操作函数,包括 `strlen`、`strcpy`、`strncpy`、`strcat`、`strncat`、`strcmp`、`strncpy`、`sprintf`、`itoa`、`strchr`、`strspn`、`strcspn`、`strstr` 和 `strtok`。每个函数均提供了语法说明、参数解释、返回值描述及示例代码。此外,还给出了部分函数的自实现版本,帮助读者深入理解其工作原理。通过这些函数,可以轻松地进行字符串长度计算、复制、连接、比较等操作。
    |
    25天前
    |
    SQL 关系型数据库 C语言
    PostgreSQL SQL扩展 ---- C语言函数(三)
    可以用C(或者与C兼容,比如C++)语言编写用户自定义函数(User-defined functions)。这些函数被编译到动态可加载目标文件(也称为共享库)中并被守护进程加载到服务中。“C语言函数”与“内部函数”的区别就在于动态加载这个特性,二者的实际编码约定本质上是相同的(因此,标准的内部函数库为用户自定义C语言函数提供了丰富的示例代码)
    |
    1月前
    |
    C语言
    【C语言】字符串及其函数速览
    【C语言】字符串及其函数速览
    25 4
    |
    1月前
    |
    编译器 程序员 C语言
    【C语言篇】从零带你全面了解函数(包括隐式声明等)(下篇)
    ⼀般情况下,企业中我们写代码时候,代码可能⽐较多,不会将所有的代码都放在⼀个⽂件中;我们往往会根据程序的功能,将代码拆分放在多个⽂件中。
    |
    1月前
    |
    测试技术 C语言
    C语言中的void函数
    C语言中的void函数
    |
    1月前
    |
    存储 安全 编译器
    C语言中的scanf函数
    C语言中的scanf函数