白话C语言之函数

简介: 白话C语言之函数

大家好这里是三岁,C语言是各类语言的基础,由于个人学习需要特别开设了C语言基础专题,内容不一定深入,都是基础知识,还是白话C语言,最简单的方式带领大家学习不是那么容易理解的C语言!


函数



什么是函数呢???

和数学里面的一样?y=kx+b?

我认为在编程中这种理解不一定对。整个编程的内容都算是数学中的函数,都是数据处理的过程。(个人理解)

那么我们说的函数是什么呢?


我认为是编程人员偷懒的法宝!

记得编程届有这样子一个说法:一段代码只重复3遍以上就应该封装成一个函数,这样子可以减少很多代码量。


只要把一些具有相同规律的内容或具象化的模块进行打包封装形成一个函数,这样子就可以简单的调用,增加代码的可读性同时易于维护。


在C语言中函数具有两方面的用途:


  • 第一种:字面意思:即求得一个函数的结果。
  • 第二种:完成一个具体的功能,相当于一个子函数。


要求一个函数的结果的值或者要运行该函数来完成相应的功能,则称该函数被掉用或调用了该函数。


函数的分类


1、用户使用角度:

  • 标准函数(库函数):
    系统定义的不需要用户自己去定义,可以直接使用他们。如:sin()、sqrt()、fabs()
  • 用户自定义函数:用户为了特定需求去完成的函数。


2、从函数类型分类

  • 无参函数:函数定义的括号中没有参数
  • 有参函数:函数名后面的括号没有参数


白话:两个函数的分类标准不一样

其实就是一个是系统写好了的可以直接使用。(类似于python中的自带类库函数)

另外一个就是自己或者开源开发者提供的(类似于第三方类库)

还有一种就是看看函数有没有输入数据,有就是有参的没有就是无参的

具体的还是接下去看看吧


函数的定义


组成:函数说明部分和函数体部分。

函数说明部分主要组成有函数类型、函数名和形式参数等三部分。

函数体主要就是函数内的局部变量的声明和执行语句组成。用来完成具体的功能。


  • 无参函数
类型说明符 函数名(){
    局部变量声明;
    函数体(语句执行部分);
}


举例:

void print_hello(){
    printf("hello world");
}


  • 有参函数
类型说明符 函数名(形参列表){
    局部变量声明;
    函数体(语句执行部分);
}


int max(int x, int y){
    int z;
    if(x>y){  //判断x和y
        z=x;  //成立就把x赋值给z
    }
    else{
        z=y;  //不成立就把y赋值给z
    }
    return z;  //返回z
}


  • 空函数
类型说明符 函数名(形参列表){
}


特殊的函数,实际意义,起到占位符的作用,待后续添加。

类似于python中的pass


说明

  • 函数类型可以是合法的数据类型说明符用于确实函数的类型
  • 函数名可以是任意的合法标识符(数字字母下划线,其中数字不可开头)
  • C语言可以有多个函数组成但是main()(主函数)有且只有一个
  • 函数之间可以相互调用,但是不能够嵌套不能够调用主函数(main())


形参和实参


名词解释:形参和实参

形参和实参直接白话吧

形参就是我们定义的函数后面括号里面的内容如上面max()里面的int x,int y一样他们有具体的定义和内容吗,但是他们门没有实际的意义,更加类似于一张板凳,上面有姓名标签,只能够对应的“人”坐上去。

实参:指的是调用函数过程中实际赋值给函数的值,如max(12,5)这里的12和5就代表实际参数。他们就是对应做到形参板凳上的“人”,当然数据类型要相同不然会报错或者结果出现偏差。


返回值 — return


如果函数调用过程中需要返回数据就需要使用return函数不然默认没有返回值。让我们看看返回值有哪些要求。

  • 函数没有说明类型的时候以整型处理
  • 返回值以定义的函数类型为准,不相同的进行强制转换(注:这里可能会产生数据偏差)
  • 如果不调用则没有返回值
  • 如果不使用返回值的话可以使用void类型进行定义


函数的调用


格式

函数名(实参列表)

  • 如果是无参函数不需要添加实参列表
  • 形参和实参需要一一对应,个数数据类型也要也要,实参之间需要用,隔开。

调用:需要函数名加括号有参的要加参数列表,这个函数可以是独立的语句,可以是一个函数的实参等

main{
    print_hello();
    max(2,max(5,6);
    c=max(12,11);
    print("%d",c)
}


结果:

hello world

6

12


调用函数的声明


当需要调用非本文件的函数时就需要使用调用声明。


如调用库函数:

需要在开头添加#include命令

例如

#include<stdio.h>


以上是非本文件的函数,如果是本文件的函数呢?

首先看是否在主函数以前,如果是则不需要,如果不是就需要进行提前声明(如果是整数和字符型数据则不需要!)。


举例:


#include<stdio.h>
int main()
{
    int x=12,y=3,z;  //定义数值及初始化值
    z=add(x,y);  //调用值
    printf("%d\n",z);  //输出结果
}
int add(int x,int y){  //定义add函数
  return x+y;  //返回结果
}

20210427203057955.png


这里自定义函数在主函数后面但是他是整型数据所以可以不需要重新定义。

如果把int类型改成float呢?


#include<stdio.h>
int main()
{
    int x=12,y=3,z;
    z=add(x,y);
    printf("%d\n",z);
}
float add(int x,int y){
  return x+y;
}


20210427203442937.png

发生了数据引用冲突。

解决办法一:


#include<stdio.h>
int main()
{
    int x=12,y=3,z;
    float add(int x,int y);  //添加定义
    z=add(x,y);
    printf("%d\n",z);
}
float add(int x,int y){
  return x+y;
}


20210427203625830.png


在main函数添加一个定义值,相对应做了一个全局的声明,然后就可以继续执行了。

方法二:


#include<stdio.h>
float add(int x,int y){
  return x+y;
}
int main()
{
    int x=12,y=3,z;
    z=add(x,y);
    printf("%d\n",z);
}


20210427203750572.png


把add函数的位置上调这样子的话就可以在使用前进行编译。

方法三:

#include<stdio.h>
float add(int x,int y);  //提前定义声明
int main()
{
    int x=12,y=3,z;
    z=add(x,y);
    printf("%d\n",z);
}
float add(int x,int y){
  return x+y;
}


2021042720452190.png


局部变量和全局变量


函数体内说明的变量只适用于该函数内,而且只在声明以后有效,这样子的被称为局部变量。

C语言规定如下:

  • main函数中定义的变量也是局部变量,只能够在主函数中有效。
  • 函数定义时的形式参数也是局部变量,只能够在说明他的函数中使用。
  • 不同的函数可以有相同的局部变量,他们不会相互影响,代表不同的对象
  • 在复合语句块中定义的变量只能够在复合语句块中有效。


白话:

没有进行全局变量的内容只有在所在的大括号(花括号)中有效果,离开了大括号则无意义。需要再重新进行定义。


全局变量:需要再文档前进行声明,这样子,声明后面的全部范围都有效。

当局部变量和全局变量名字相同时只有局部变量有效


变量的存储类型


除了之前提到的数据类型还需要了解变量的存储类型。


存储类型 C语言声明词
动态存储 auto
静态存储 static
外部存储 extern
寄存器变量 register


如果用存储的时间来考虑就是静态存储动态存储

静态存储就相当于主席台上的位置,一场活动都是专属的座位,需要到活动结束以后才能够释放。

动态存储就好像是场地上的座位,按照需求进行一个动态的分配的,在人离开凳子后(函数调用结束后)系统就会收回然后分配给下一个需的人。


变量的存储类型声明:

存储类型 数据类型 变量名


1、自动变量(动态变量)(auto)

格式:

auto 数据类型 变量名


示例:

auto int a;


数据存储中如果没有使用存储类型的声明则默认为auto

auto存储在动态存储区,主要是指函数的动态局部变量形式参数

auto在函数调用开始分配内存,函数调用结束释放内存。

普通的内部变量均隐含的被认为是auto类型,一般很少使用auto特异说明。


2、局部静态变量(static)

格式:

static 数据类型 变量名


示例:

static int a;


静态变量存储在静态存储区,在其所在的程序和函数中是永久变量,如果在调用函数后想保留值继续下次使用可应该为静态存储。

示例:

#include<stdio.h>
int f(int b){
  auto int a=0;  // 定义动态变量 a 
  static int c=3;  // 定义静态变量c 
  a+=1;c+=1;  // a、c完成自加 
  return (a+b+c);  // 返回a、b、c的和 
}
int main(){
    int b=2,i;
    for(i;i<=3;i++){  // 循环 
        printf("%d\n",f(b));  // 输出f()的运行结果 
  }
}


次数 a的值 b的值 c的值 返回值
第一次开始 0 2 3 /
第一次结束 1 2 4 7
第二次开始 0 2 4 /
第二次结束 1 2 5 8
第三次开始 0 2 5 /
第三次结束 1 2 6 9
第四次开始 0 2 6 /
第四次结束 1 2 7 10


20210428000347583.png


解析:

a值是f()函数的局部动态变量。从上图可以看出 在f()函数结束以后内存释放,再次运行后重新赋值

c值是f()函数的局部静态变量、从上图可以看出 在f()函数结束以后内存没有释放,再次运行后还保存了上次的数据b是主函数的局部动态变量,虽然是局部变量但是分为两部分。

  • 第一部分是主函数定义的局部动态整型变量
  • 第二部分是给f函数的实参,转变成了f函数内部的局部动态整型变量

两次变量由于主函数没有结束最后的结果也是没有发生改变,直到程序结束以后才释放内存。


3、寄存器变量(register)

格式:

register 数据类型 变量名


由于数据存储在运行内存运算速度会快的多,可以提高执行效率。所以有了寄存器变量。

要求:

  • 1、寄存器变量的类型:intchar 或者是指向这两种的指针。
  • 2、仅适用于函数的局部变量和函数的形式参数。
  • 3、C语言规定寄存器的数量不能够超过3个,多余的按照auto处理。


4、外部变量(extern)

格式:

extern 数据类型 变量名


在定义全局变量时如果没有特别声明,则默认为extern

系统只给extern类型分配一次变量,如果已经分配会寻找同名的数据进行处理,不会再次分配。


5、静态全局变量(static)

格式:

static 数据类型 变量名


这个和上面的区别类似于publicprivate外部变量是公共的而静态全局变量是私有的。


递归函数



递归函数就是函数直接或间接调用自己形成类似于套娃的内容。如果设计得当是一个比较好的程序,设计不好,那就是bug聚集地和死循环乐园,容易被老板开除。


需要满足下面3个条件:

  • 原问题能够向另外一个更加简单的问题进行转换
  • 转换后的问题和原问题求解类似
  • 转换后能后有一个终止条件

举例:最经典的阶乘问题


#include<stdio.h>
long fac(int n){  //定义fac函数 
  long f;
  printf("n=%d\t",n);  //输出当前n的值(大->小) 
  if(n<0){
    printf("\nn<0,数据错误!");  //判断是否小于0 
    f=0;   //赋值为0 
  }
  else if(n==0)f=1;  //最后停止的标志 
  else f=fac(n-1)*n;  //数据没问题调用自己继续执行 
  printf("\n 返回:n=%d\tf=%d",n,f); //当调用到n==0后 开始执行 
  return f;
} 
main(){
  int n;
  long y;
  printf("需要输出的轮数:");
  scanf("%d",&n);
  y=fac(n);  //调用fac函数 
  if(y!=0){  //y!=0就输出 
    printf("\n%d!=%ld\n",n,y);
  } 
}


2021042801102934.png

该方法比for循环占用的时间更短,相对来说更加合适,但是和技术想法等都有关系。


就上面的函数停止条件是else if(n==0)f=1; //最后停止的标志这一句以后才停止。如果这里不把握好就会出现与预期不符的情况。


好啦,这次的函数就到这里,量比较大,如果是第一次学习语言,需要好好理解。相当于就是把经常用到的东西或者比较繁琐的东西单独处理,然后再到main函数进行组装的一个过程。


预告:下一课 :数组!

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